协变和逆变的简单例子

发布于 2024-10-11 15:00:21 字数 107 浏览 4 评论 0 原文

有人能给我提供协变、逆变、不变性和反不变性的简单 C# 示例吗(如果存在的话)。

到目前为止我看到的所有示例都只是将一些对象转换为System.Object

Could someone provide me simple C# examples of convariance, contravariance, invariance and contra-invariance (if such thing exists).

All samples I've seen so far was just casting some object into System.Object.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

ま柒月 2024-10-18 15:00:21

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

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

下面是协变的示例:

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

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

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

下面是逆变的示例:

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

Action 委托是逆变的。 Frog 可转换为 Animal 的事实意味着 Action 可转换为 Action。请注意,这种关系与协变关系的方向相反;这就是为什么它是“contra”变体。由于可转换性,此代码成功;在 C# 3 中它会失败。

这应该是有道理的。该动作可以采取任何动物;我们需要一个可以采取任何青蛙的行动,并且可以采取任何动物的行动肯定也可以采取任何青蛙。

不变性的一个例子:

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

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

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

http://blogs.msdn.com/b/ericlippert/存档/标签/协方差+和+逆变/

Could someone provide me simple C# examples of convariance, contravariance, invariance and contra-invariance (if such thing exists).

I have no idea what "contra-invariance" means. The rest are easy.

Here's an example of covariance:

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

The IEnumerable<T> interface is covariant. The fact that Giraffe is convertible to Animal implies that IEnumerable<Giraffe> is convertible to IEnumerable<Animal>. Since List<Giraffe> implements IEnumerable<Giraffe> this code succeeds in C# 4; it would have failed in C# 3 because covariance on IEnumerable<T> did not work in C# 3.

This should make sense. A sequence of Giraffes can be treated as a sequence of Animals.

Here's an example of contravariance:

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

The Action<T> delegate is contravariant. The fact that Frog is convertible to Animal implies that Action<Animal> is convertible to Action<Frog>. Notice how this relationship is the opposite direction of the covariant one; that's why it is "contra" variant. Because of the convertibility, this code succeeds; it would have failed in C# 3.

This should make sense. The action can take any Animal; we need an action that can take any Frog, and an action that can take any Animal surely can also take any Frog.

An example of invariance:

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

Can we pass an IList<Giraffe> to this thing? No, because someone is going to write a Tiger into it, and a Tiger cannot be in a list of Giraffes. Can we pass an IList<Animal> into this thing? No, because we are going to read a Mammal out of it, and a list of Animals might contain a Frog. IList<T> is invariant. It can only be used as what it actually is.

For some additional thoughts on the design of this feature, see my series of articles on how we designed and built it.

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

2024-10-18 15:00:21

不变性(在这种情况下)是指不存在协变和逆变。所以“反不变性”这个词没有意义。任何未标记为 inout 的类型参数都是不变的。这意味着该类型参数既可以被使用也可以被返回。

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

对于逆变,您可以使用任何消费接口或委托。我想到了 IComparerAction。它们从不返回 T 类型的变量,仅接收它。无论您希望在何处接收 Base,都可以传入 Derived

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

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

Invariance(in this context) is the absence of both co- and contra-variance. So the word contra-invariance doesn't make sense. Any type parameter that's not tagged as either in or out is invariant. This means this type parameter can both be consumed and returned.

A good example of co-variance is IEnumerable<out T> because an IEnumerable<Derived> can be substituted for an IEnumerable<Base>. Or Func<out T> which returns values of type T.
For example an IEnumerable<Dog> can be converted to IEnumerable<Animal> because any Dog is an animal.

For contra-variance you can use any consuming interface or delegate. IComparer<in T> or Action<in T> come to my mind. These never return a variable of type T, only receive it. Wherever you expect to receive a Base you can pass in a Derived.

Thinking of them as input-only or output-only type-parameters makes it easier to understand IMO.

And the word invariants is typically not used together with type variance, but in the context of class or method invariants, and represents a conserved property. See this stackoverflow thread where the differences between invariants and invariance are discussed.

吾家有女初长成 2024-10-18 15:00:21

如果您考虑泛型的常规使用,您会经常使用接口来处理对象,但该对象是类的实例 - 您无法实例化该接口。使用简单的字符串列表作为示例。

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

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

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

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

这显然无法编译,因为泛型类型参数 IListList 不同。即使您将外部列表声明为常规类,例如 List>,它也不会编译 - 类型参数不匹配。

这就是协方差可以发挥作用的地方。协方差允许您使用更多派生的类型作为此表达式中的类型参数。如果将 IList<> 设为协变,则可以简单地编译并解决问题。不幸的是,IList<> 不是协变的,但它扩展的接口之一是:

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

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

If you consider the regular use of generics, you regularly use an interface to handle an object, but the object is an instance of a class - you can't instantiate the interface. Use a simple list of strings as an example.

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

I'm sure you're aware of the advantages of using an IList<> rather than directly using a List<>. It allows for inversion of control, and you might decide you don't want to use a List<> any longer, but you want a LinkedList<> instead. The above code works fine because the generic type of both the interface and class is the same: string.

It can get a bit more complicated if you want to make a list of list of string though. Consider this example:

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

This clearly won't compile, because the generic types arguments IList<string> and List<string> are not the same. Even if you declared the outer list as a regular class, like List<IList<string>>, it would not compile - the type arguments do not match.

So here's where covariance can help. Covariance allows you to use a more derived type as the type argument in this expression. If IList<> was made to be covariant, it would simlpy compile and fix the problem. Unfortunately, IList<> is not covariant, but one the interfaces it extends is:

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

This code now compiles, the type arguments are the same as they were above.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文