2

我从这里复制了代码: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”类型的委托参数。

4

1 回答 1

1

正如我之前在博客上回答的那样,这里的主要问题是何时RuntimeInitialize()调用 PostSharp 还不知道该类将使用哪些通用参数进行初始化。但是,当OnConstructorEntry()被调用时,我们确实有此信息。有关 PostSharp 方面如何工作的更多信息,请务必阅读有关方面生命周期的文档

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

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

为了知道您需要向哪个事件添加处理程序,您需要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()。由于方面代码由泛型类型的所有实例“共享”(它们使用相同的范围),因此您应该分别缓存每个实例类型。例如,使用 a Dictionary<Type, Action<object>>,其中Type指的是实例类型,并且Action<object>是可以将空事件处理程序添加到实例的方法。

这正是我现在在我的库中使用的实现,你可以在 github 上找到更新的版本。正如您将看到的,我使用了一个CachedDictionary类,它已经为您处理了大部分缓存逻辑,因为它是一种常见的场景。之前失败的单元测试现在成功了。

于 2012-11-05T17:17:42.873 回答