簡體   English   中英

C#使用約束泛型僅允許對父類型使用函數

[英]C# use constraint generics to allow function only on parent types

我試圖使用泛型約束,以僅允許對另一種類型的父類型調用泛型函數。

例:

public class SomeClass<Derived> 
    where Derived : class
{
    public void call<Parent>()
        where Parent : class
    {
        ParenthoodChecker<Derived, Parent> checker =
            new ParenthoodChecker<Derived, Parent>();
    }
}

public class ParenthoodChecker<Derived, Parent> 
    where Parent  : class 
    where Derived : Parent
{
    public ParenthoodChecker()
    {
    }
}

目前,我收到以下錯誤消息:

錯誤CS0311:類型'Derived'不能用作通用類型或方法'ParenthoodChecker'中的類型參數'Derived'。 沒有從“派生”到“父母”的隱式引用轉換。 (CS0311)

有什么方法可以強制執行這樣的事情嗎? 我希望在運行時不進行檢查,我覺得編譯器應該可以推斷出這一點。

您面臨的問題是,通用約束( where關鍵字)僅與使用它的類/方法的通用參數相關聯。 因此,在編寫通用方法call<Parent> ,只能在參數Parent上定義約束。

您可以通過在方法中添加一個新的人工泛型參數來解決該問題-這會使簽名復雜化,但最終從語法角度使簽名正確:

public class SomeClass<Derived>
    where Derived : class
{
    public void call<Parent, NewDerived>()
        where Parent : class
        where NewDerived: Derived, Parent
    {
        ParenthoodChecker<NewDerived, Parent> checker =
            new ParenthoodChecker<NewDerived, Parent>();
    }
}

我認為,除了丑陋之外,除了增加復雜性之外,此解決方案也不會引起錯誤的行為。 NewDerived類型仍為Derived

在更高的理論基礎上,這是比較值的問題:如果A > BA > C ,我們能說出B > C嗎? -顯然不是因為這里沒有描述BC之間的精確關系。

相反,如果您告訴Parent > NewDerivedDerived > NewDerived ,那就很好了。 但是,您仍然會缺少“ Parent > Derived的證明。 這就是為什么(我認為)不可能編寫這樣的函數(使編譯器認為Parent實際上是Derived的超類型)的全部原因。

通過上面給出的實現,您甚至可以用Derived代替NewDerived來調用方法:

class A { }
class B : A { }

SomeClass<B> s = new SomeClass<B>();
s.call<A, B>();

在此示例中,只有兩個類AB ,因此甚至沒有其他任何類扮演虛擬NewDerived的角色。 整個操作保留在類型A (作為基礎)和類型B (作為派生)之間。

我認為考慮使用ParenthoodChecker可以在編譯時進行以下檢查:

public class Parent { }
public class Derived : Parent { }
public class NotDerived { }

Parent p1 = (Derived)null; // This will compile
Parent p2 = (NotDerived)null; // This won't compile

大多數答案給出了部分答案,因此在這里我將匯總所有答案以及為什么它們不適用於特定情況的原因。

選項1 :倒轉主叫方和被叫方(m.rogalski的評論)

public class SomeClass<Parent> //instead of derived
    where Parent : class
{
    public void call<Derived>()
        where Derived : class, Parent
    {
        ParentChecker<Derived, Parent> checker =  new ParentChecker<Derived, Parent>();
    }
}

當然,這是最接近完整編譯時間檢查的方法,但是根據系統設計(我的情況),這可能不可行。

選項2 :使用中間模板類型(佐蘭·霍瓦特的答案)

public class SomeClass<Derived>
    where Derived : class
{
    public void call<Parent, NewDerived>()
        where Parent : class
        where NewDerived: Derived, Parent
    {
        ParentChecker<NewDerived, Parent> checker =
        new ParentChecker<NewDerived, Parent>();
    }
}

只要您以正確的輸入(大多數情況下)使用它,它就可以工作,但是使用編譯時間檢查的原因主要是為了避免誤用某些東西,因此我認為這不是一個完整的答案。 可用性比理想情況弱(您需要在通話時同時指定兩種類型),並且具有潛在的反例,例如:

public interface GrandP_A {}
public interface GrandP_B {}
public class Parent : GrandP_A {}
public class Child : Parent, GrandP_B {} 

SomeClass<Parent> instance = new SomeClass<Parent>();
instance.call<GrandP_B, Child>();//this compiles

SomeClass的目標是檢查SomeClass的泛型參數是從調用函數的第一個泛型類型派生的。 在這種情況下,為GrandP_B。 但是,GrandP_B不是Parent的父類,但是該調用仍會編譯。 同樣,這只有在使用得當時才有效(將Parent作為.call的第二種通用​​類型)

選項3 :運行時檢查

我不得不妥協並改用運行時檢查。 顯然,這不是采用編譯時解決方案的方法,而是唯一允許我想到的特定設計的方法。 我仍然會在這里提及它作為部分答案,以防將來對某人有所幫助。 有關更多信息,請在該特定問題上查看其他答案

public class SomeClass<Derived> 
    where Derived : class
{
    public void call<Parent>()
        where Parent : class
    {
        if(typeof(Derived).IsSubclassOf(typeof(Parent)))
        {
            //do your stuff
        }
        else
        {
            throw new Exception("Must be called wih parent classes only!");
        }
    }
}

編譯器在這里與傳遞的實際類型名稱混淆:

public class ParenthoodChecker<Derived, **Parent**> 
        where Parent : class 
        where Derived : Parent

    {
        public ParenthoodChecker()
        { }
    }

我們通常會傳遞一個通用類型,並使用where子句指定該類型的約束。

嘗試這個:

 public class ParenthoodChecker<Derived, TParent>        
        where Derived : Parent
        where TParent : class 
    {
        public ParenthoodChecker()
        { }
    }

暫無
暫無

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

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