2012-08-12 74 views
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:类型的ParameterExpression“TValue”不能被用于类型的委托参数创建emptyDelegate的行上的'TValue'。

+0

我正在讲这个问题。有趣的是,我在寻找帮助时结束了这篇文章。 :) – 2012-11-05 16:50:33

回答

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现在成功。

+0

非常感谢Steven,出色的工作! – Anupheaus 2012-11-07 15:23:33