使用泛型类型动态创建空事件委托

时间:2012-08-12 09:38:44

标签: c# events generics delegates postsharp

我已经从这里复制了代码:https://whathecode.wordpress.com/2012/03/26/null-checks-for-event-handlers-an-aspect-solution/

但是当事件在一般类型的类中时,我似乎无法使它工作。我有一个类定义为:

Public Class MultiKeyDictionary<TFirstKey, TSecondKey, TValue>

以及以下事件:

public delegate void EventDelegate(TValue value);

public delegate void ReplacedEventDelegate(TValue oldValue, TValue newValue);

public event EventDelegate Added;

public event EventDelegate Removed;

public event ReplacedEventDelegate Replaced;

但初始化代码异常抱怨类型的ContainsGenericParameters设置为true(或类似的东西)。

我已将RuntimeInitialize方法中该链接中的代码更改为:

public override void RuntimeInitialize(EventInfo eventInfo) {
    base.RuntimeInitialize(eventInfo);
    Type eventType;
    MethodInfo delegateInfo = eventInfo.EventHandlerType.MethodInfoFromDelegateType();          
    ParameterExpression[] parameters = delegateInfo.GetParameters().Select(p => Expression.Parameter(p.ParameterType)).ToArray();
    if(eventInfo.EventHandlerType.ContainsGenericParameters) {
        var genericDelegate = eventInfo.EventHandlerType.GetGenericTypeDefinition();
        var genericParams = genericDelegate.GetGenericArguments();
        eventType = genericDelegate.MakeGenericType(genericParams);
    } else {
        eventType = eventInfo.EventHandlerType;
    }
    Delegate emptyDelegate = Expression.Lambda(eventType, Expression.Empty(), "EmptyDelegate", true, parameters).Compile();
    this.addEmptyEventHandler = instance => eventInfo.AddEventHandler(instance, emptyDelegate);
}

但我现在得到的只是一个ArgumentException:类型'TValue'的ParameterExpression不能用于创建emptyDelegate的行上'TValue'类型的委托参数。

1 个答案:

答案 0 :(得分:1)

正如我之前在博客上回复的那样,这里的主要问题是调用RuntimeInitialize()时PostSharp还不知道该类将被初始化的泛型参数。但是,当调用OnConstructorEntry()时,我们确实拥有此信息。有关PostSharp方面如何工作的更多信息,请务必阅读the documentation on Aspect lifetimes

在现有代码中,当在运行时为类创建方面(RuntimeInitialize()方法)时,我只是忽略了类可以是通用的事实。这是我的疏忽。您无法使用Expression.Lambda编译“泛型”类型,因此无法编译可由泛型类型的所有不同实例使用的公共事件处理程序。

您需要在运行时为分别为泛型类型的每个不同实例化编译此空事件处理程序。这可以在OnConstructorEntry中完成,您可以从PostSharp传递的MethodExecutionArgs参数中接收实例类型。

为了了解您需要添加处理程序的事件,您需要在运行时初始化时将EventInfo存储在您的方面。

[NonSerialized]
EventInfo _event;

通过比较名称,您知道该方面适用于哪个事件。以下是目前OnConstructorEntry()的工作代码。

Type runtimeType = args.Instance.GetType();
EventInfo runtimeEvent =
    runtimeType.GetEvents().Where( e => e.Name == _event.Name ).First();

MethodInfo delegateInfo =
    DelegateHelper.MethodInfoFromDelegateType( runtimeEvent.EventHandlerType );
ParameterExpression[] parameters = delegateInfo
    .GetParameters()
    .Select( p => Expression.Parameter( p.ParameterType ) )
    .ToArray();
Delegate emptyDelegate = Expression.Lambda(
    runtimeEvent.EventHandlerType, Expression.Empty(),
    "EmptyDelegate", true, parameters ).Compile();

// Add the empty handler to the instance.
MethodInfo addMethod = runtimeEvent.GetAddMethod( true );
if ( addMethod.IsPublic )
{
    runtimeEvent.AddEventHandler( args.Instance, emptyDelegate );
}
else
{
    addMethod.Invoke( args.Instance, new object[] { emptyDelegate } );
}

这仍然存在一个问题。每次构造类型时我们都不想做所有这些反射!因此,理想情况下,您应该缓存在RuntimeInitialize()中添加空处理程序的方法。由于方面代码由泛型类型的所有实例共享(它们使用相同的范围),因此应分别为每个实例类型缓存。例如。使用Dictionary<Type, Action<object>>,其中Type引用实例类型,Action<object>是可以将空事件处理程序添加到实例的方法。

这正是我现在在我的库of which you can find the updated version on github中使用的实现。正如您将看到的,我使用了一个CachedDictionary类,它可以处理大部分缓存逻辑,因为它是如此常见的情况。 previously failing unit test现在成功了。

相关问题