[英]Why is the generic parameter not casting?
我遗漏了一些基本的东西,但我无法理解。 鉴于:
abstract class EventBase {}
class SpecialEvent : EventBase {}
在另一个类中我希望允许调用者能够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);
}
}
但是, _validTypes.Add
行无法编译。 它无法将Action<T>
转换为Action<EventBase>
。 约束指定T
必须从EventBase
派生,所以我误解了什么?
C#是不正确的。 要了解原因,请考虑以下情况:
// 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?
}
让我们假设C#允许您为Action<SpecialEvent>
传递Action<EventBase>
。 接下来会发生什么:
PureEvil(SpecialAction); // Not allowed
现在, PureEvil
将尝试传递NotSoSpecialEvent
以委托使用SpecialAction
SpecialEvent
,而这必须永远不会发生。
动作委托是逆变的 - 类型被定义为Action<in T>
。 这对于替代原则在应用于行动时如何运作具有重要意义。
考虑两种类型: B
(基础)和D
(派生)。 然后Action<B>
比Action<D>
派生更多 。 这意味着以下行为:
class B { }
class D : B { }
...
Action<D> derivedAction = ...
Action<B> baseAction = derivedAction; // Fails
Action<D> derivedAction1 = baseAction; // Succeeds
在您的示例中,如果SpecialEvent
是从EventBase
派生的类型,则只允许分配Action<SpecialEvent> = Action<EventBase>
,而不是相反(当您尝试时)。
由于在将代理存储在字典中之前已经检查了事件类型,因此您不必坚持存储强类型代理 - 更不用说由于Action
代理的逆转而不能坚持强类型。
您可以在字典中存储任何您喜欢的内容,例如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>
不能用作Action<EventBase>
。
使用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);
}
}
像这样使用它:
var handler = new Action<SpecialEvent>(ev => { Console.WriteLine("works!"); });
FooHandler.RegisterFor<SpecialEvent>(handler);
FooHandler.ExecuteHandler(new SpecialEvent());
你有一个Action<SpecialEvent>
,只知道处理SpecialEvent
。 Action<EventBase>
表示可以将任何 EventBase
传递给它。 这使得转换不安全。
在您的情况下,我会选择Dictionary<Type, Delegate>
,其中每个T
键与Action<T>
值配对。 如果您可以确保只添加正确类型的值,则可以在要调用它时将代理安全地转换回Action<T>
。
如果Dictionary
中的一些Action<EventBase>
是Action<UnexpectedEvent>
而不是Action<SpecialEvent>
怎么办? Action<T>
是逆变的,但它不是协变的。
请参阅: https : //blogs.msdn.microsoft.com/ericlippert/2007/10/16/covariance-and-contravariance-in-c-part-one/
如果您碰巧知道类型应该解决,和/或在类型冲突时想要一个异常,您可以将它包装在另一个执行强制转换的操作中,如下所示:
_validTypes.Add(typeof(T), eventBase => handlerFcn((T)eventBase));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.