通用方法可以使用逆变/协变类型吗?

时间:2011-11-11 20:54:06

标签: c# oop generics .net-4.0 t4

我正在编写一个通用方法,以便在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. 有没有改变这种行为的技巧?

3 个答案:

答案 0 :(得分:21)

这个问题很混乱。让我看看我是否可以澄清它。

  

当我尝试实现IGreatInterface时,编译器会为aMethodBeta()标记错误,因为我使用IAnInterface的子类型创建了该方法我想实现这样的方法:{ {1}}。

这不合法。简化:

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); } 不符合Vegetarian的合同。您应该能够将任何食物传递给吃,但IEater只接受水果。 C#不支持虚方法形式参数协方差,因为它不是类型安全的。

现在,您可能会说,这是怎么回事:

Vegetarian

现在我们有了类型安全; interface IFruitEater { void Eat(Fruit fruit); } class Omnivore : IFruitEater { public void Eat(Food food); } 可以用作Omnivore,因为IFruitEater可以吃水果以及任何其他食物。

不幸的是,C#不支持虚方法形式参数类型逆变,即使这样做在理论上是类型安全的。很少有语言支持这一点。

同样,C#也不支持虚方法返回类型方差

我不确定这是否真的回答了你的问题。你能澄清这个问题吗?

更新:

怎么样:

Omnivore

不,这也不合法。 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> T Food。你不能部分实施合同,而不是你可以这样做:

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)泛型类型是委托或接口。

答案 1 :(得分:2)

如果您想从通用界面继承,请参阅phoog的答案。如果你正在谈论尝试同时实现一个接口,那么我将在下面进行讨论。

假设:

internal interface IAnInterface { }

public class SomeSubClass : IAnInterface { }

public class AnotherSubClass : IAnInterface { }

public GreatClass : IGreatInterface { ... }

尝试使用更多派生(共变量)参数实现接口的问题是,当通过接口调用此接口时,传入的IAnInterface将是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
    {

    }
}

当您从接口实现泛型方法时,不允许这样做,约束必须匹配。

答案 2 :(得分:0)

也许你正在寻找这个:

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

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

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

编辑:

是的,你是对的:如果在接口中定义泛型方法,则无法使用兼容类型的具体方法实现该方法。

如何使用委托(因为代表支持共同和逆转):

[示例已删除,因为我向后调整了方差 - 它不起作用。]