42

有人可以为我提供简单的 C# 示例,例如协变、逆变、不变性和逆变性(如果存在这种情况)。

到目前为止,我看到的所有样本都只是将一些对象投射到System.Object.

4

3 回答 3

89

有人可以为我提供简单的 C# 示例,例如协变、逆变、不变性和逆变性(如果存在这种情况)。

我不知道“反不变性”是什么意思。其余的都很容易。

下面是一个协方差的例子:

void FeedTheAnimals(IEnumerable<Animal> animals) 
{ 
    foreach(Animal animal in animals)
        animal.Feed();
}
...
List<Giraffe> giraffes = ...;
FeedTheAnimals(giraffes);

IEnumerable<T>接口是协变的。Giraffe 可转换为 Animal 的事实意味着IEnumerable<Giraffe>可转换为IEnumerable<Animal>. 由于List<Giraffe>实现IEnumerable<Giraffe>此代码在 C# 4 中成功;它在 C# 3 中会失败,因为协方差在IEnumerable<T>C# 3 中不起作用。

这应该是有道理的。一系列长颈鹿可以被视为一系列动物。

下面是一个逆变的例子:

void DoSomethingToAFrog(Action<Frog> action, Frog frog)
{
    action(frog);
}
...
Action<Animal> feed = animal=>{animal.Feed();}
DoSomethingToAFrog(feed, new Frog());

Action<T>委托是逆变的。Frog 可转换为 Animal 的事实意味着Action<Animal>可转换为Action<Frog>. 注意这种关系是如何与协变关系相反的;这就是为什么它是“对立”变体。由于可兑换,此代码成功;它在 C# 3 中会失败。

这应该是有道理的。该动作可以采取任何动物;我们需要一个可以带任何 Frog 的动作,一个可以带任何 Animal 的动作肯定也可以带任何 Frog。

不变性的一个例子:

void ReadAndWrite(IList<Mammal> mammals)
{
    Mammal mammal = mammals[0];
    mammals[0] = new Tiger();
}

我们可以传递一个IList<Giraffe>给这个东西吗?不,因为有人要在其中写入老虎,而老虎不能出现在长颈鹿列表中。我们可以传递一个IList<Animal>到这个东西吗?不,因为我们要从中读取哺乳动物,而动物列表可能包含一只青蛙。 IList<T>不变的。它只能按实际情况使用。

有关此功能设计的其他想法,请参阅我关于我们如何设计和构建它的系列文章。

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

于 2011-01-12T17:37:32.577 回答
4

不变性(在这种情况下)是不存在协方差和反方差。所以反不变这个词没有意义。任何未标记为in或者out是不变的类型参数。这意味着该类型参数既可以使用也可以返回。

协方差的一个很好的例子是IEnumerable<out T>因为 a IEnumerable<Derived>可以代替IEnumerable<Base>。或者Func<out T>返回类型的值T
例如 anIEnumerable<Dog>可以转换为,IEnumerable<Animal>因为任何 Dog 都是动物。

对于逆变,您可以使用任何消费接口或委托。IComparer<in T>Action<in T>浮现在我的脑海。这些从不返回类型变量T,只接收它。无论您希望在哪里收到 a Base,都可以传入 a Derived

将它们视为仅输入或仅输出类型参数可以更容易地理解 IMO。

并且不变量这个词通常不与类型变化一起使用,而是在类或方法不变量的上下文中使用,并表示一个守恒属性。请参阅此 stackoverflow 线程,其中讨论了不变量和不变性之间的差异。

于 2011-01-12T14:47:17.390 回答
2

如果你考虑泛型的常规使用,你经常使用一个接口来处理一个对象,但是这个对象是一个类的一个实例——你不能实例化接口。以一个简单的字符串列表为例。

IList<string> strlist = new List<string>();

我相信您知道使用 aIList<>而不是直接使用 a的优势List<>。它允许控制反转,您可能决定List<>不再使用 a ,而是想要 a LinkedList<>。上面的代码工作正常,因为接口和类的泛型类型是相同的:string.

如果你想创建一个字符串列表,它可能会变得更复杂一些。考虑这个例子:

IList<IList<string>> strlists = new List<List<string>>();

这显然不会编译,因为泛型类型的参数IList<string>List<string>不一样。即使您将外部列表声明为常规类,例如List<IList<string>>,它也不会编译 - 类型参数不匹配。

所以这里是协方差可以提供帮助的地方。协方差允许您使用更派生的类型作为此表达式中的类型参数。如果IList<>使其成为协变的,它将简单地编译并修复问题。不幸的是,IList<>它不是协变的,但它扩展的接口之一是:

IEnumerable<IList<string>> strlists = new List<List<string>>();

此代码现在可以编译,类型参数与上面相同。

于 2011-01-12T14:51:02.713 回答