簡體   English   中英

通用方法可以使用逆變/協變類型嗎?

[英]A generic method can use contravariant/covariant types?

我正在編寫一個通用方法,在T4模板的特殊任務中使用它。 該方法應該允許我使用通用接口中的專用類型。 我想到了以下簽名:

interface IGreatInterface {
    Object aMethodAlpha<U>(U parameter) where U : IAnInterface;
    Object aMethodBeta(IAnInterface parameter)
}

public class AnInterestingClass : IAnInterface{}

當我嘗試實現IGreatInterface ,編譯器為aMethodBeta()標記了一個錯誤,因為我已經讓我的T4使用IAnInterface的子類型編寫該方法(即我想實現這樣的方法: Object aMethodBeta(AnInterestingClass parameter) )。

方法aMethodAlpha<U>()可以使用,但不是我想要的干凈,因為我的T4必須生成一些額外的代碼。 我(也許錯誤地)提出必須由T4完成該方法的實現
Object aMethodAlpha<AnInterestingClass>(AnInterestingClass parameter)

我認為泛型方法不支持逆變類型,但我不確定; 我想這是編譯器阻止編碼器使用具有未在通用類型中定義的方法的特定類型的方式...

  1. 通用方法在實現時是否必須使用確切類型?
  2. 有什么技巧可以改變這種行為嗎?

這個問題很混亂。 讓我看看我是否可以澄清它。

當我嘗試實現IGreatInterface ,編譯器為aMethodBeta()標記了一個錯誤,因為我使用IAnInterface的子類型IAnInterface了該方法我想實現這樣的方法: Object aMethodBeta(AnInterestingClass parameter)

那不合法。 有點簡化:

class Food {}
class Fruit : Food {}
class Meat : Food {}
interface IEater
{
    void Eat(Food food);
}
class Vegetarian : IEater
{
    public void Eat(Fruit fruit);
}

Class Vegetarian不符合IEater的合同。 你應該可以通過任何食物吃,但Vegetarian只接受水果。 C#不支持虛方法形式參數協方差,因為它不是類型安全的。

現在,你可能會說,這是怎么回事:

interface IFruitEater
{
    void Eat(Fruit fruit);
}
class Omnivore : IFruitEater
{
    public void Eat(Food food);
}

現在我們有了類型安全; Omnivore可以用作IFruitEater因為Omnivore可以吃水果以及任何其他食物。

不幸的是,C#不支持虛方法形式參數類型的逆變,即使這樣做在理論上是類型安全的。 很少有語言支持這一點。

同樣,C#也不支持虛方法返回類型方差

我不確定這是否真的回答了你的問題。 你能澄清一下這個問題嗎?

更新:

關於什么:

interface IEater
{
    void Eat<T>(T t) where T : Food;
}
class Vegetarian : IEater
{
    // I only want to eat fruit!
    public void Eat<Fruit>(Fruit food) { }
}

不,那也不合法。 IEater的合同是你將提供一種方法Eat<T> ,可以采取任何Food T 您無法部分實施合同,只能執行此操作:

interface IAdder
{
    int Add(int x, int y);
}
class Adder : IAdder
{
    // I only know how to add two!
    public int Add(2, int y){ ... }
}

但是,您可以這樣做:

interface IEater<T> where T : Food
{
    void Eat(T t);
}
class Vegetarian : IEater<Fruit>
{
    public void Eat(Fruit fruit) { }
}

這完全合法。 但是,你做不到:

interface IEater<T> where T : Food
{
    void Eat(T t);
}
class Omnivore : IEater<Fruit>
{
    public void Eat(Food food) { }
}

因為C#不支持虛方法形式參數逆變或協方差。

請注意,C# 確實支持參數多態協方差 ,因此已知類型安全。 例如,這是合法的:

IEnumerable<Fruit> fruit = whatever;
IEnumerable<Food> food = fruit;

一系列水果可用作一系列食物。 要么,

IComparable<Fruit> fruitComparer = whatever;
IComparable<Apples> appleComparer = fruitComparer;

如果你有可以比較任何兩個水果的東西,那么它可以比較任何兩個蘋果。

然而,這種協方差和逆變只有在以下所有條件都成立時才合法:(1)方差可證明是類型安全的,(2)類型的作者添加了方差注釋,表明所需的共同和方差,( 3)所涉及的變量類型參數都是引用類型,(4)泛型類型是委托或接口。

如果您想從通用接口繼承,請參閱phoog的答案。 如果你正在談論嘗試共同實現一個接口,這導致我在下面的討論。

假設:

internal interface IAnInterface { }

public class SomeSubClass : IAnInterface { }

public class AnotherSubClass : IAnInterface { }

public GreatClass : IGreatInterface { ... }

嘗試使用更多派生(共變體)參數實現接口的問題是,當通過接口調用此接口時,傳入的IAnInterface將是SomeSubClass實例,這是不SomeSubClass 這就是為什么它不允許直接。

IGreatInterface x = new GreatClass();

x.aMethodBeta(new AnotherSubClass());

如果你可以做協方差,這會失敗,因為你會期待一個SomeSubClass但會得到一個AnotherSubClass

可以做的是做明確的接口實現:

class GreatInterface : IGreatInterface
{
    // explicitly implement aMethodBeta() when called from interface reference
    object IGreatInterface.aMethodBeta(IAnInterface parameter)
    {
        // do whatever you'd do on IAnInterface itself...
        var newParam = parameter as SomeSubClass;

        if (newParam != null)
        {
            aMethodBeta(newParam);
        }

        // otherwise do some other action...
    }

    // This version is visible from the class reference itself and has the 
    // sub-class parameter
    public object aMethodBeta(SomeSubClass parameter)
    {
        // do whatever
    }
}

因此,如果你這樣做,你的接口支持泛型,該類有一個更具體的方法,但仍然支持該接口。 主要區別在於您需要處理傳入IAnInterface的意外實現的情況。

更新 :聽起來你想要這樣的東西:

public interface ISomeInterface
{
    void SomeMethod<A>(A someArgument);
}

public class SomeClass : ISomeInterface
{
    public void SomeMethod<TA>(TA someArgument) where TA : SomeClass
    {

    }
}

當您從接口實現泛型方法時,不允許這樣做,約束必須匹配。

也許你正在尋找這個:

interface IGreatInterface<in U> where U : IAnInterface
{ 
    Object aMethodAlpha(U parameter);
} 

class SomeClass : IAnInterface { /*...*/ }

class GreatClass : IGreatInterface<SomeClass>
{
    public Object aMethodAlpha(SomeClass parameter) {}
}

編輯:

是的,你是對的:如果在接口中定義泛型方法,則無法使用兼容類型的具體方法實現該方法。

如何使用代表(因為代表支持共同和逆轉):

[示例已刪除,因為我向后調整了方差 - 它不起作用。]

暫無
暫無

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

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