Weak Delegates 2
After toying with weak delegates a bit more, I found that although my previous implementation was clean and useful, it was abhoridly slow. After a bit of thinking, I came up with a new method that creates a WeakReference within a closure, and generates the rest of the code.
Here some some statistics (1000000 iterations):
- New Weak Delegate Wrapper: 129ms
- Calling .Invoke on a MethodInfo: 751ms
- My original implementation: 2848ms
How it works
The principal is simple: rather than creating a delegate during execution time, or invoking the MethodInfo
object
on the WeakReference
, we'll create a WeakReference
instances, and create a new lambda expression that takes
the same exact parameters as the original method, but evaluates the WeakReference
before calling them.
Because we no longer have scope on the evaluation itself, and because we don't want to make any assumptions about
the caller's implementation, we also pass in ifDisposed
, which is a paramaterless action that will be invoked
if we attempt to invoke the weak delegate after it has been garbage collected.
Code Generation Implementation
Without further adieu, here is my weak delegate wrapper generator:
public static TDelegate Wrap<TDelegate>(TDelegate method, Action ifDisposed)
where TDelegate : class
{
//Verify delegate is a delegate
if (!typeof(TDelegate).IsSubclassOf (typeof(Delegate)))
throw new InvalidOperationException (typeof(TDelegate).Name + " is not of type Delegate");
//Verify delegate is not weak itself (eg, must be class method)
var realDelegate = method as Delegate;
if (realDelegate.Method.GetCustomAttributes (typeof(CompilerGeneratedAttribute), true).Length > 0)
throw new InvalidOperationException ("Delegate must not have its own closure, and must be a class-method");
//Create wrap to delegate type that takes 'this' as a parameter
var parameters = realDelegate.Method.GetParameters ();
ParameterExpression[] args = new ParameterExpression[parameters.Length];
for (int i=0; i<parameters.Length; ++i)
{
args[i] = Expression.Parameter(parameters[i].ParameterType, parameters[i].Name);
}
//Create a weak reference and resolving target
var expWeakRef = Expression.Constant (new WeakReference (realDelegate.Target, false));
var expWeakTarget = Expression.Convert (Expression.Property (expWeakRef, "Target"), realDelegate.Method.DeclaringType);
//Create a forked expression to evaluate the weak method
var expInvokeFork =
Expression.Condition(
Expression.NotEqual(expWeakTarget, Expression.Constant(null)),
Expression.Call ( expWeakTarget, realDelegate.Method, args),
Expression.Block(
Expression.Invoke(Expression.Constant(ifDisposed)),
Expression.Default(realDelegate.Method.ReturnType)
)
);
return Expression.Lambda (typeof(TDelegate), expInvokeFork, args).Compile () as TDelegate;
}