2012-08-12 78 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