为什么通用参数不能投射?

时间:2018-02-23 23:28:29

标签: c# .net-core

我遗漏了一些基本的东西,但我无法理解。给出:

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派生,那么我有什么误解呢?

5 个答案:

答案 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>,只知道处理SpecialEventAction<EventBase>表示任何 EventBase都可以传递给它。这使得转换不安全。

在您的情况下,我会选择Dictionary<Type, Delegate>代替,其中每个T密钥与Action<T>值配对。如果您可以确保只添加正确类型的值,则可以在需要调用时将代理安全地转发回Action<T>

答案 4 :(得分:0)

如果Action<EventBase>中的某些DictionaryAction<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));