Article Image
read

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;
}
Blog Logo

Christopher LaPointe


Published

Image

Chris LaPointe

Another site of Code

Back to Overview