簡體   English   中英

C# 泛型約束:class 上的類型參數繼承方法上的類型參數?

[英]C# generic constraint: type parameter on the class inherits type parameter on the method?

我有一個通用的 class,我在其上定義了一個方法,該方法應該采用另一種類型的 arguments,但前提是另一種類型實現了 class 的類型參數。 但是,這不會編譯:

class GenericClass<TClass>
{
    public void DoSomething<TMethod>(TMethod input) where TClass : TMethod
    {
        // ...
    }
}

在方法的約束中,我在TClass上遇到編譯器錯誤。 還有另一種方法來指定這個嗎?

澄清:
我的印象是TMethod: TClass意味着TMethod必須繼承或實現TClass (取決於TClass是具體類型還是接口)。 在其他稍微非常規的符號TMethod > TClass (意味着TMethodTClass的超集)。

我在這里想要的是,如果TClass繼承或實現它,則TMethod應該是有效類型,即TClass > TMethod TMethod: TClass也會這樣做嗎?

(其中一個答案指出TMethod: TClass要求TMethod可以TClass分配。我不確定這是否符合我的要求,但如果滿足,請更詳細地解釋什么是可分配的意思,因為如果它對我有幫助大概是我誤會了……)

執行摘要:

Chris Hannon 的回答基本上是正確的。 但是,有一些涉及接口和擴展方法的偷偷摸摸的技巧可能會得到你想要的東西。

過多的細節:

我們偶爾會被問到這種約束,還有其他語言也有這樣的約束。 Java 和我相信 Scala 都有辦法說“這必須是那個的超類型”。 正如其他人指出的那樣,這種約束不能在 C# 類型系統或底層 CLR 類型系統中表示。

您可能需要這種差異的原因是針對以下情況:

class ImmutableStack<T>
{
    public static ImmutableStack<T> Empty { get { return empty; } }
    private static ImmutableStack<T> empty = new ImmutableStack<T>();
    private T head;
    private ImmutableStack<T> tail;
    public ImmutableStack<T> Pop()
    {
        if (this == empty) throw new Exception();
        return this.tail;
    }
    public ImmutableStack<N> Push(N value) where T : N // not legal
    {
        var result = new ImmutableStack<N>();
        result.head = value;
        result.tail = this; // also not legal
        return result;
    }
}

現在您可以執行以下操作:

Tiger tony = new Tiger();
Elephant dumbo = new Elephant();
ImmutableStack<Tiger> tigers = ImmutableStack<Tiger>.Empty;
tigers = tigers.Push<Tiger>(tony);
ImmutableStack<Mammal> mammals = tigers.Push<Mammal>(dumbo);

嘿,現在你可以把一頭大象推到一堆老虎身上,它神奇地變成了一堆哺乳動物!

這在 C# 中是不可能的,因為我在這里展示了兩個原因:首先,因為沒有這樣的通用約束,其次,因為不可變的 class 類型不是協變的。 (分配給尾部需要協方差。)

但是,如果您很狡猾,可以在 C# 中執行此操作 您可以通過將所有內容表示為協變接口而不是 class 來解決協方差問題。 您可以通過將有問題的方法移出 class 並使其成為擴展方法來解決“無此類約束”問題:我在這里舉了一個示例:

http://blogs.msdn.com/b/ericlippert/archive/2007/12/06/immutability-in-c-part-three-a-covariant-immutable-stack.aspx

你不能在 C# 中做你要求做的事情。 約束本質上指定了某事物必須具有的公共可分配點,並且約束 TMethod: TClass 將意味着您知道 TMethod 至少可以被視為 TClass。

將 TMethod 約束為“在另一種類型的 inheritance 結構中的某個位置”的問題在於,就約束 go 而言,您現在沒有好的方法來確定所給出的基本類型。 您是否使用由完全不同類型的三層深度的基礎 class 實現的特定接口? 您是否傳遞了基礎 class? 您是否傳遞了一個不相關的后代 class 其基礎恰好落入對象的 inheritance 結構中? Object is the closest you'll get that matches all possible combinations, and at that point you may as well just forget about using generics and use Object directly because all of the type-safety benefits provided by generics have been thrown out the window.

反過來也會破壞多態性的目的。 如果方法簽名需要 class A 並且我的類型從 class A 下降,為什么不允許我將其作為 A 傳遞? 如果反之亦然,並且方法簽名可能期望 B 的任何基本 class 並指定 B,那么如果您能夠傳入 A,則您不能合理地期望 B 的任何功能都可用。

您不能這樣做,因為TClass未在方法類型簽名中定義。 TClass 在TClass級別定義,因此您不能在方法級別對其進行約束。

嘗試...

class GenericClass<TClass>
{
    public void DoSomething<TMethod>(TMethod input) where TMethod : TClass
    {
        // ...
    }
}

這就是說,允許在可從 TClass 分配 TMethod 的地方調用此方法。

我認為最接近的方法是通過 Object 並對 inheritance 關系執行運行時檢查。

class GenericClass<TClass>
{
    public void DoSomething(Object input)
    {
        if (input == null) throw new ArgumentNullException("input");
        if (!input.GetType().IsAssignableFrom(typeof(TClass)))
            throw new ArgumentException("Input type must be assignable from " + typeof(TClass).ToString(), "input");
        // ...
    }
}

請注意,這將允許輸入類型為 Object 以及(在極少數情況下使用透明遠程代理)TClass 實現的接口類型。 這些也可以被明確排除。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM