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(); }
}
