[EN] WeakDelegate for all types of delegates

Opublikowany 6 marca 2011 w C#

As I promised in one of my previous posts I’m showing you corrected WeakDelegate<TDelegate> which is working with all types of delegates. The implementation was less challenging than I expected. Fortunately everything could be done with Expression Trees and usage of dynamic was not necessary.
And now it’s time for some source code:

public class WeakDelegate<TDelegate>
	where TDelegate : class {
	private readonly WeakReference _weakTarget;
	private readonly TDelegate _proxyDelegate;
	private Action<TDelegate> _cleanUp;

	public WeakDelegate( TDelegate @delegate, Action<TDelegate> cleanUp ) {
		var d = @delegate as Delegate;

		if( d == null )
			throw new ArgumentNullException( "delegate" );

		if( d.Target == null || d.Method == null || d.Method.IsStatic )
			throw new ArgumentException(
				"Can't create weak delegate to static method." );

		this._weakTarget = new WeakReference( d.Target );
		this._cleanUp = cleanUp;

		var targetType = d.Target.GetType();
		var returnType = d.Method.ReturnType;

		var lambdaArgs = d.Method.GetParameters()
			.Select( ( p, i ) =>
				Expression.Parameter( p.ParameterType, "a" + i ) )
			.ToArray();

		var targetVar = Expression.Variable( targetType, "target" );

		var getTargetCall = Expression.Invoke( (Expression)
			this.GetType()
			.GetMethod( "GetTargetBoundExpression",
				BindingFlags.Instance | BindingFlags.NonPublic )
			.MakeGenericMethod( targetType )
			.Invoke( this, new object[ 0 ] ) );

		var returnLabel = Expression.Label( returnType );
		var defaultReturn = Expression.Default( returnType );

		var lambda = Expression.Lambda<TDelegate>( Expression.Block(
			new[] { targetVar },
			new Expression[] {
				Expression.Assign( targetVar, getTargetCall ),
					Expression.IfThenElse(
						Expression.Equal( targetVar, Expression.Constant( null ) ),
						Expression.Return( returnLabel, defaultReturn ),
						Expression.Return( returnLabel,
							Expression.Call( targetVar, d.Method, lambdaArgs ) ) ),
					Expression.Label( returnLabel, defaultReturn )
				} ), lambdaArgs );

		this._proxyDelegate = lambda.Compile();
	}

	public static implicit operator TDelegate(
		WeakDelegate<TDelegate> weakDelegate ) {
		return weakDelegate != null ? weakDelegate._proxyDelegate : null;
	}

	private Expression GetTargetBoundExpression<TTarget>() {
		return (Expression<Func<TTarget>>) ( () => this.GetTarget<TTarget>() );
	}

	private TTarget GetTarget<TTarget>() {
		var target = (TTarget) this._weakTarget.Target;
		if( target != null )
			return target;

		if( this._cleanUp != null ) {
			this._cleanUp( this._proxyDelegate );
			this._cleanUp = null;
		}

		return target;
	}
}

In lines from 23 to 50 using types read from original delegate and expression trees I’m creating proxy TDelegate simmilar to the following metacode:

this._proxyDelegate = (a0...aN) => {
	var target = this.GetTarget<TTarget>();
	if( target == null )
		return default( TDelegateReturnType );
	else
		return target.OriginalDelegateMethod( a0...aN );
}

The implementation is sill quite fast – only 6-9 times slower than direct delegate invocation. It has a lower performance than WeakEventHandler<TEventHandler> described in one of my previous posts which is only 4-6 times slower.
There are also few drawbacks of my solution:

  • after the target is garbage collected, the proxy delegate will return default value of the TDelegate‘s return type (I think that this can be solved somehow using for example cleanUp callback)
  • cleaning up is not thread safe etc.

The source code with examples is here.

Podziel się na:
  • RSS
  • Twitter
  • Facebook
  • dotnetomaniak.pl
  • develway.pl
  • Blip
  • Grono.net
  • Spinacz
  • LinkedIn
  • Technorati
  • del.icio.us
  • PDF
  • Drukuj
  • email
komentarze: 0 » tagi: ,

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

*

Komentarz

Możesz używać tych znaczników i atrybutów HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>