簡體   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