通用方法可以使用逆变/协变类型吗?
我正在编写一个通用的方法,在T4模板的特殊任务中使用它。 该方法应该允许我使用通用接口中的专用类型。 我想到了以下签名:
interface IGreatInterface { Object aMethodAlpha(U parameter) where U : IAnInterface; Object aMethodBeta(IAnInterface parameter) } public class AnInterestingClass : IAnInterface{}
当我尝试实现IGreatInterface
,编译器为aMethodBeta()
标记了一个错误,因为我已经让我的T4使用IAnInterface
的子类型来编写该方法(即我想实现这样的方法: Object aMethodBeta(AnInterestingClass parameter)
)。
方法aMethodAlpha()
可以使用,但不是我想要的干净,因为我的T4必须生成一些额外的代码。 我(也许错误地)提出必须由T4完成该方法的实现
Object aMethodAlpha(AnInterestingClass parameter)
。
我认为generics方法不支持逆变类型,但我不确定; 我想这是编译器阻止编码器使用具有未在通用类型中定义的方法的特定类型的方式…
- 通用方法在实现时是否必须使用确切类型?
- 有什么技巧可以改变这种行为吗?
这个问题很混乱。 让我看看我是否可以澄清它。
当我尝试实现
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#不支持虚方法forms参数协方差,因为它不是类型安全的。
现在,你可能会说,这是怎么回事:
interface IFruitEater { void Eat(Fruit fruit); } class Omnivore : IFruitEater { public void Eat(Food food); }
现在我们有了类型安全; Omnivore
可用作IFruitEater
因为Omnivore
可以吃水果以及任何其他食物。
不幸的是,C#不支持虚方法forms参数类型逆变,即使这样做在理论上是类型安全的。 很少有语言支持这一点。
同样,C#也不支持虚方法返回类型方差 。
我不确定这是否真的回答了你的问题。 你能澄清一下这个问题吗?
更新:
关于什么:
interface IEater { void Eat(T t) where T : Food; } class Vegetarian : IEater { // I only want to eat fruit! public void Eat(Fruit food) { } }
不,那也不合法。 IEater
的合同是你将提供一种方法Eat
,可以采取任何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 where T : Food { void Eat(T t); } class Vegetarian : IEater { public void Eat(Fruit fruit) { } }
这完全合法。 但是,你做不到:
interface IEater where T : Food { void Eat(T t); } class Omnivore : IEater { public void Eat(Food food) { } }
因为C#不支持虚方法forms参数逆变或协方差。
请注意,C# 确实支持参数多态协方差 ,因此已知类型安全。 例如,这是合法的:
IEnumerable fruit = whatever; IEnumerable food = fruit;
一系列水果可用作食物序列。 要么,
IComparable fruitComparer = whatever; IComparable appleComparer = fruitComparer;
如果你有可以比较任何两个水果的东西,那么它可以比较任何两个苹果。
然而,这种协方差和逆变只有在满足以下所有条件时才合法:(1)方差可certificate是类型安全的,(2)类型的作者添加了方差注释,表明所需的共同和反差,( 3)所涉及的变化类型参数都是引用类型,(4)generics类型是委托或接口。
如果您想从通用接口inheritance,请参阅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 } }
因此,如果你这样做,你的接口支持generics,该类有一个更具体的方法,但仍然支持该接口。 主要区别在于您需要处理传入IAnInterface
的意外实现的情况。
更新 :听起来你想要这样的东西:
public interface ISomeInterface { void SomeMethod(A someArgument); } public class SomeClass : ISomeInterface { public void SomeMethod(TA someArgument) where TA : SomeClass { } }
这是不允许的,当您从接口实现generics方法时,约束必须匹配。
也许你正在寻找这个:
interface IGreatInterface where U : IAnInterface { Object aMethodAlpha(U parameter); } class SomeClass : IAnInterface { /*...*/ } class GreatClass : IGreatInterface { public Object aMethodAlpha(SomeClass parameter) {} }
编辑:
是的,你是对的:如果在接口中定义generics方法,则无法使用兼容类型的具体方法实现该方法。
如何使用代表(因为代表支持共同和逆转):
[示例已删除,因为我向后调整了方差 – 它不起作用。]