I’ve copied the code from here: https://whathecode.wordpress.com/2012/03/26/null-checks-for-event-handlers-an-aspect-solution/
But I can’t seem to get it to work when the event is within a generically typed class. I have a class defined as:
Public Class MultiKeyDictionary<TFirstKey, TSecondKey, TValue>
and the following events:
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;
But the initialisation code exceptions complaining that the type’s ContainsGenericParameters is set to true (or something similar to that).
I’ve changed the code in that link in the RuntimeInitialize method to this:
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);
}
But all I get now is an ArgumentException: ParameterExpression of type ‘TValue’ cannot be used for delegate parameter of type ‘TValue’ on the line creating emptyDelegate.
As I replied earlier on my blog, the main problem here is when
RuntimeInitialize()is called PostSharp doesn’t know yet with which generic arguments the class will be initialized. However whenOnConstructorEntry()is called we do have this information. For more information about how PostSharp aspects work, be sure to read the documentation on Aspect lifetimes.In the existing code, when the aspect is created at runtime for the class (the
RuntimeInitialize()method) I simply ignored the fact that the class could be generic. This was on oversight on my part. You can’t compile ‘generic’ types usingExpression.Lambda, hence it is impossible to compile a common event handler which can be used by all different instantiations of the generic type.You need to compile this empty event handler at run time for each different instantiation of the generic type separately. This can be done in
OnConstructorEntrywhere you can receive the instance type from theMethodExecutionArgsparameter passed by PostSharp.In order to know which event you need to add a handler to, you need to store
EventInfoin your aspect at runtime initialization.You know which event the aspect applies to by comparing names. What follows is currently working code from
OnConstructorEntry().This still leaves open one problem. We do not want to do all this reflection every time the type is constructed! Therefore ideally you should cache the method which adds the empty handler as previously in
RuntimeInitialize(). Since the aspect code is ‘shared’ by all instantiations of the generic type (they use the same scope), you should cache for each instance type separately. E.g. using aDictionary<Type, Action<object>>, whereTyperefers to the instance type andAction<object>is the method which can add the empty event handler to the instance.This is exactly the implementation I now use in my library, of which you can find the updated version on github. As you will see I use a
CachedDictionaryclass, which handles most of the caching logic already for you since it is such a common scenario. The previously failing unit test now succeeds.