我遗漏了一些基本的东西,但我无法理解。给出:
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
派生,那么我有什么误解呢?
答案 0 :(得分:6)
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
,而indexPath.row
必须永远不会发生。{/ p>
答案 1 :(得分:4)
动作委托是逆变的 - 类型定义为duration
。这对于替代原则在应用于行动时如何运作具有影响。
考虑两种类型: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
,但不能反过来(正如您尝试的那样)。< / p>
由于您已经在将代表存储在字典中之前检查了事件类型,因此您不必坚持存储强类型代理 - 更不用说由于{{的逆转而不能坚持强类型1}}代表。
您可以在字典中存储您喜欢的任何内容,例如Action<SpecialEvent> = Action<EventBase>
或普通Action
,然后在从集合中获取Delegate
委托时,向下转换为具体的object
。
Action<T>
答案 2 :(得分:2)
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());
答案 3 :(得分:2)
你有一个Action<SpecialEvent>
,只知道处理SpecialEvent
。 Action<EventBase>
表示任何 EventBase
都可以传递给它。这使得转换不安全。
在您的情况下,我会选择Dictionary<Type, Delegate>
代替,其中每个T
密钥与Action<T>
值配对。如果您可以确保只添加正确类型的值,则可以在需要调用时将代理安全地转发回Action<T>
。
答案 4 :(得分:0)
如果Action<EventBase>
中的某些Dictionary
是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));