简体   繁体   English

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

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

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