首先,我已经阅读了许多关于 SO 的解释以及关于协变和逆变的博客,非常感谢Eric Lippert制作了这么多关于协变和逆变的系列。
但是,我有一个更具体的问题,我想稍微了解一下。
据我了解,Eric 的解释是 Covariance 和 Contravariance 都是描述转换的形容词。协变变换是保留类型顺序的变换,而逆变变换是反转它的变换。
我以我认为大多数开发人员直观理解的方式理解协方差。
//covariant operation
Animal someAnimal = new Giraffe();
//assume returns Mammal, also covariant operation
someAnimal = Mammal.GetSomeMammal();
这里的返回操作是协变的,因为我们保留了两个 Animal 仍然大于 Mammal 或 Giraffe 的大小。关于这一点,大多数返回操作都是协变的,逆变操作没有意义。
//if return operations were contravariant
//the following would be illegal
//as Mammal would need to be stored in something
//equal to or less derived than Mammal
//which would mean that Animal is now less than or equal than Mammal
//therefore reversing the relationship
Animal someAnimal = Mammal.GetSomeMammal();
对于大多数开发人员来说,这段代码当然没有意义。
我的困惑在于逆变参数参数。如果你有一个方法,比如
bool Compare(Mammal mammal1, Mammal mammal2);
我一直都知道输入参数总是强制逆变行为。这样,如果将类型用作输入参数,则其行为应该是逆变的。
但是下面的代码有什么区别
Mammal mammal1 = new Giraffe(); //covariant
Mammal mammal2 = new Dolphin(); //covariant
Compare(mammal1, mammal2); //covariant or contravariant?
//or
Compare(new Giraffe(), new Dolphin()); //covariant or contravariant?
出于同样的原因,你不能做这样的事情你不能做
//not valid
Mammal mammal1 = new Animal();
//not valid
Compare(new Animal(), new Dolphin());
我想我要问的是,是什么让方法参数通过逆变转换。
对不起,很长的帖子,也许我理解错了。
编辑:
根据下面的一些对话,我了解例如使用委托层可以清楚地显示逆变性。考虑以下示例
//legal, covariance
Mammal someMammal = new Mammal();
Animal someAnimal = someMammal;
// legal in C# 4.0, covariance (because defined in Interface)
IEnumerable<Mammal> mammalList = Enumerable.Empty<Mammal>();
IEnumerable<Animal> animalList = mammalList;
//because of this, one would assume
//that the following line is legal as well
void ProcessMammal(Mammal someMammal);
Action<Mammal> processMethod = ProcessMammal;
Action<Animal> someAction = processMethod;
当然这是非法的,因为有人可以将任何 Animal 传递给 someAction,而 ProcessMammal 期望任何是 Mammal 或更具体(小于 Mammal)的东西。这就是为什么 someAction 只能是 Action 或更具体的 (Action)
然而,这在中间引入了一层代表,为了实现逆变投影,是否必须在中间有一个代表?如果我们将 Process 定义为一个接口,我们会将参数参数声明为逆变类型,只是因为我们不希望有人能够做我上面展示的委托?
public interface IProcess<out T>
{
void Process(T val);
}