Why is the generic parameter not running?

I am missing something basic, but I cannot understand. Given:

abstract class EventBase {} class SpecialEvent : EventBase {} 

In another class, I want to allow callers to RegisterFor<SpecialEvent>(x => {...})

 public class FooHandler { { internal Dictionary<Type, Action<EventBase>> _validTypes = new Dictionary<Type, Action<EventBase>>(); internal void RegisterFor<T>(Action<T> handlerFcn) where T: EventBase { _validTypes.Add(typeof(T), handlerFcn); } } 

However, the _validTypes.Add string _validTypes.Add not compile. It cannot convert Action<T> to Action<EventBase> . The restriction indicates that T should be obtained from EventBase , so what I don’t understand?

+7
c # .net-core
source share
5 answers

C # is right to prohibit this. To understand why, consider this situation:

 // This is your delegate implementation void SpecialAction(SpecialEvent e) { Console.WriteLine("I'm so special!"); } // This is another EventBase class class NotSoSpecialEvent : EventBase {} void PureEvil(Action<EventBase> handlerFcn) where T: EventBase { handlerFcn(new NotSoSpecialEvent()); // Why not? } 

Suppose C # allows you to pass an Action<SpecialEvent> to an Action<EventBase> . Here's what will happen next:

 PureEvil(SpecialAction); // Not allowed 

Now PureEvil will try to pass NotSoSpecialEvent SpecialAction delegate, which accepts SpecialEvent , which should never be.

+6
source share

Action delegates are contravariant β€” the type is defined as Action<in T> . This implies how the principle of substitution works when applied to actions.

Consider two types: B (base) and D (received). Then Action<B> more output than Action<D> . This implies the following behavior:

 class B { } class D : B { } ... Action<D> derivedAction = ... Action<B> baseAction = derivedAction; // Fails Action<D> derivedAction1 = baseAction; // Succeeds 

In your example, when SpecialEvent is a type derived from EventBase , you are allowed to assign Action<SpecialEvent> = Action<EventBase> , but not vice versa (as you try).

Since you are already checking the type of event before storing delegates in the dictionary, you do not need to insist on storing strongly typed delegates, not to mention that you cannot insist on strong printing due to the contravariance of Action delegates.

You can store everything you like in the dictionary, for example. Delegate or plain object , and then go down to a specific Action<T> when it comes time to select an Action delegate from the collection.

 public class FooHandler { internal Dictionary<Type, object> _validTypes = new Dictionary<Type, object>(); internal void RegisterFor<T>(Action<T> handlerFcn) where T: EventBase { _validTypes.Add(typeof(T), handlerFcn); } internal Action<T> Find<T>() where T : EventBase => (Action<T>)_validTypes[typeof(T)]; } 
+4
source share

Action<SpecialEvent> cannot be used as an Action<EventBase> .

Use Delegate to abstract the parameters and then discard them:

 public class FooHandler { internal Dictionary<Type, Delegate> _validTypes = new Dictionary<Type, Delegate>(); internal void RegisterFor<T>(Action<T> handlerFcn) where T : EventBase { _validTypes.Add(typeof(T), handlerFcn); } internal void ExecuteHandler<T>(T value) where T : EventBase { var handler = (Action<T>)_validTypes[typeof(T)]; handler(value); } } 

Use it as follows:

 var handler = new Action<SpecialEvent>(ev => { Console.WriteLine("works!"); }); FooHandler.RegisterFor<SpecialEvent>(handler); FooHandler.ExecuteHandler(new SpecialEvent()); 
+2
source share

You have an Action<SpecialEvent> that is known to handle SpecialEvent . Action<EventBase> means that any EventBase can be passed to it. This makes the conversion unsafe.

In your case, I would choose Dictionary<Type, Delegate> instead, where each key T associated with the value Action<T> . If you can only provide the addition of values ​​of the correct type, you can safely give the delegate back to Action<T> if you want to call it.

+2
source share

What if any Action<EventBase> in the Dictionary is an Action<UnexpectedEvent> instead of an Action<SpecialEvent> ? Action<T> is contravariant, but it is not covariant.

see: https://blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/

If you know that types should work and / or want an exception if there are type conflicts, you can wrap it in another throwing action, for example:

 _validTypes.Add(typeof(T), eventBase => handlerFcn((T)eventBase)); 
0
source share

All Articles