繁体   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