.NET: RegexMarshalByRefWrapper class

Regular expressions created with RegexOptions.Compiled will leak, unless they're housed in a secondary AppDomain that can be unloaded -- you'll need this wrapper class, or something like it, to do that.

A similar technique can be used with System.CodeDom.Compiler types.

This simple little C# class is a remotable wrapper around the System.Text.RegularExpressions.Regex class.  Usage looks something like this:

object[] reCtorArgs = new object[] { 
    "[A-Za-z]+[A-Za-z0-9]*", RegexOptions.Compiled };

RegexMarshalByRefWrapper re = 
    (RegexMarshalByRefWrapper)ad2.CreateInstanceAndUnwrap(
    "ThisAssembly", "RegexMarshalByRefWrapper", false, 
    BindingFlags.Default, null, reCtorArgs, null, null, null);

re.IsMatch("foo");

Motivation:  When using RegexOptions.Compiled, the code generated via Reflection.Emit is never garbage-collectable by the CLR.  The only way to unload JIT-compiled code in .NET is to unload an entire AppDomain.  But the Regex class is [Serializable], so it will be marshaled by-value back into our own AppDomain if we naively attempt to instantiate a Regex remotely.

Long-running applications (think: services) which need to execute user-supplied regexes on large volumes of text could suffer from this.

A "remotable wrapper" is needed -- all remotable objects must derive from the System.MarshalByRefObject class, but because .NET does not support multiple inheritance for classes, we can't also derive from Regex, at the same time.  :-(  Oh well, the only difference between is-a and has-a relationships is a lot of typing!  So here we go...

using System;
using System.Text.RegularExpressions;

public class RegexMarshalByRefWrapper : MarshalByRefObject
{
    private Regex _re;
    
    public RegexMarshalByRefWrapper(string pattern)
    { _re = new Regex(pattern); }
    public RegexMarshalByRefWrapper(string pattern, RegexOptions options)
    { _re = new Regex(pattern, options); }

    public RegexOptions Options
    { get { return _re.Options; } }
    public bool RightToLeft
    { get { return _re.RightToLeft; } }

    public string[] GetGroupNames()
    { return _re.GetGroupNames(); }
    public int[] GetGroupNumbers()
    { return _re.GetGroupNumbers(); }

    public string GroupNameFromNumber(int i)
    { return _re.GroupNameFromNumber(i); }
    public int GroupNumberFromName(string name)
    { return _re.GroupNumberFromName(name); }

    public bool IsMatch(string input)
    { return _re.IsMatch(input); }
    public bool IsMatch(string input, int startat)
    { return _re.IsMatch(input, startat); }

    public Match Match(string input)
    { return _re.Match(input); }
    public Match Match(string input, int startat)
    { return _re.Match(input, startat); }
    public Match Match(string input, int startat, int length)
    { return _re.Match(input, startat, length); }

    public MatchCollection Matches(string input)
    { return _re.Matches(input); }
    public MatchCollection Matches(string input, int startat)
    { return _re.Matches(input, startat); }

    public string Replace(string input, MatchEvaluator evaluator)
    { return _re.Replace(input, evaluator); }
    public string Replace(string input, string replacement)
    { return _re.Replace(input, replacement); }
    public string Replace(string input, MatchEvaluator evaluator, int count)
    { return _re.Replace(input, evaluator, count); }
    public string Replace(string input, string replacement, int count)
    { return _re.Replace(input, replacement, count); }
    public string Replace(string input, MatchEvaluator evaluator, int count, int startat)
    { return _re.Replace(input, evaluator, count, startat); }
    public string Replace(string input, string replacement, int count, int startat)
    { return _re.Replace(input, replacement, count, startat); }

    public string[] Split(string input)
    { return _re.Split(input); }
    public string[] Split(string input, int count)
    { return _re.Split(input, count); }
    public string[] Split(string input, int count, int startat)
    { return _re.Split(input, count, startat); }

    public override string ToString()
    { return _re.ToString(); }
}