繁体   English   中英

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

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM