[英]Why is the generic parameter not casting?
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 => {...})
在另一个类中我希望允许调用者能够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. 但是, _validTypes.Add
行无法编译。 It cannot convert a Action<T>
to an Action<EventBase>
. 它无法将Action<T>
转换为Action<EventBase>
。 The constraint specifies that T
must be derived from EventBase
, so what am I misunderstanding? 约束指定T
必须从EventBase
派生,所以我误解了什么?
C# is correct to disallow that. C#是不正确的。 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>
. 让我们假设C#允许您为Action<SpecialEvent>
传递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. 现在, PureEvil
将尝试传递NotSoSpecialEvent
以委托使用SpecialAction
SpecialEvent
,而这必须永远不会发生。
Action delegates are contravariant - the type is defined as Action<in T>
. 动作委托是逆变的 - 类型被定义为Action<in T>
。 That has an implication on how substitution principle works when applied to actions. 这对于替代原则在应用于行动时如何运作具有重要意义。
Consider two types: B
(base) and D
(derived). 考虑两种类型: B
(基础)和D
(派生)。 Then Action<B>
is more derived than Action<D>
. 然后Action<B>
比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). 在您的示例中,如果SpecialEvent
是从EventBase
派生的类型,则只允许分配Action<SpecialEvent> = Action<EventBase>
,而不是相反(当您尝试时)。
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. 由于在将代理存储在字典中之前已经检查了事件类型,因此您不必坚持存储强类型代理 - 更不用说由于Action
代理的逆转而不能坚持强类型。
You can store anything you like in the dictionary, eg Delegate
or plain object
and then downcast to a concrete Action<T>
when the time comes to fetch the Action
delegate from the collection. 您可以在字典中存储任何您喜欢的内容,例如Delegate
或plain object
,然后在从集合中获取Action
委托时,向下转换为具体的Action<T>
。
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)];
}
Action<SpecialEvent>
cannot be used as Action<EventBase>
. Action<SpecialEvent>
不能用作Action<EventBase>
。
Use Delegate
to abstract the parameters and then cast it back: 使用Delegate
抽象参数,然后将其强制转换:
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 like this: 像这样使用它:
var handler = new Action<SpecialEvent>(ev => { Console.WriteLine("works!"); });
FooHandler.RegisterFor<SpecialEvent>(handler);
FooHandler.ExecuteHandler(new SpecialEvent());
You've got an Action<SpecialEvent>
, which is only known to handle SpecialEvent
. 你有一个Action<SpecialEvent>
,只知道处理SpecialEvent
。 Action<EventBase>
means any EventBase
can be passed to it. Action<EventBase>
表示可以将任何 EventBase
传递给它。 That makes the conversion unsafe. 这使得转换不安全。
In your case, I'd opt for a Dictionary<Type, Delegate>
instead, where each T
key is paired with an Action<T>
value. 在您的情况下,我会选择Dictionary<Type, Delegate>
,其中每个T
键与Action<T>
值配对。 If you can ensure you only add values of the correct type, you can safely cast the delegate back to Action<T>
when you want to invoke it. 如果您可以确保只添加正确类型的值,则可以在要调用它时将代理安全地转换回Action<T>
。
What if some Action<EventBase>
in the Dictionary
is an Action<UnexpectedEvent>
instead of an Action<SpecialEvent>
? 如果Dictionary
中的一些Action<EventBase>
是Action<UnexpectedEvent>
而不是Action<SpecialEvent>
怎么办? Action<T>
is contravariant, but it is not covariant. Action<T>
是逆变的,但它不是协变的。
see: https://blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/ 请参阅: https : //blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/
If you happen to know that the types should work out, and/or want an exception if the types conflict, you can wrap it in another action that performs a cast, like so: 如果您碰巧知道类型应该解决,和/或在类型冲突时想要一个异常,您可以将它包装在另一个执行强制转换的操作中,如下所示:
_validTypes.Add(typeof(T), eventBase => handlerFcn((T)eventBase));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.