I'm missing something basic, but I can't figure it out. Given:
abstract class EventBase {}
class SpecialEvent : EventBase {}
Over in another class I want to allow callers to be able 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
line does not compile. It cannot convert a Action<T>
to an Action<EventBase>
. The constraint specifies that T
must be derived from EventBase
, so what am I misunderstanding?
The Java compiler won't let you cast a generic type across its type parameters because the target type, in general, is neither a subtype nor a supertype.
In order to use a generic type we must provide one type argument per type parameter that was declared for the generic type. The type argument list is a comma separated list that is delimited by angle brackets and follows the type name. The result is a so-called parameterized type.
As mentioned previously, generics can eliminate the requirement for casting.
A generic type is declared by specifying a type parameter in an angle brackets after a type name, e.g. TypeName<T> where T is a type parameter.
C# is correct to disallow that. 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?
}
Let's imagine that C# allows you to pass Action<SpecialEvent>
for Action<EventBase>
. Here is what would happen next:
PureEvil(SpecialAction); // Not allowed
Now PureEvil
will try passing NotSoSpecialEvent
to delegate SpecialAction
that takes SpecialEvent
, which must never happen.
Action delegates are contravariant - the type is defined as Action<in T>
. That has an implication on how substitution principle works when applied to actions.
Consider two types: B
(base) and D
(derived). Then Action<B>
is more derived 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, with SpecialEvent
being the type deriving from EventBase
, you are only allowed to assign Action<SpecialEvent> = Action<EventBase>
, but not the other way around (as you are attempting).
Since you are already checking the event type before storing the delegates in the dictionary, then you don't have to insist on storing strongly typed delegates - let alone that you cannot insist on strong typing due to contravariance of Action
delegates.
You can store anything you like in the dictionary, e.g. Delegate
or plain object
and then downcast to a concrete Action<T>
when the time comes to fetch the 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)];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With