协方差和对比方差之间的差异

jan*_*doe 144 c# covariance contravariance c#-4.0

我无法理解协方差和逆变之间的区别.

Eri*_*ert 255

问题是"协方差和逆变之间有什么区别?"

协方差和逆变是映射函数的属性,该函数将集合中的一个成员与另一个成员相关联.更具体地,映射对于该集合上的关系可以是协变的或逆变的.

考虑所有C#类型集合的以下两个子集.第一:

{ Animal, 
  Tiger, 
  Fruit, 
  Banana }.
Run Code Online (Sandbox Code Playgroud)

第二,这个明显相关的集合:

{ IEnumerable<Animal>, 
  IEnumerable<Tiger>, 
  IEnumerable<Fruit>, 
  IEnumerable<Banana> }
Run Code Online (Sandbox Code Playgroud)

存在从第一组到第二组的映射操作.也就是说,对于第一组中的每个T,第二组中的对应类型是IEnumerable<T>.或者,简而言之,映射是T ? IE<T>.请注意,这是一个"细箭头".

和我一起到目前为止?

现在让我们考虑一个关系.第一组中的类型对之间存在赋值兼容性关系.类型的值Tiger可以分配给类型的变量Animal,因此这些类型被称为"赋值兼容".让我们写一个"类型的值X可以用Y更短的形式分配给类型的变量":X ? Y.请注意,这是一个"胖箭".

所以在我们的第一个子集中,这里是所有赋值兼容性关系:

Tiger  ? Tiger
Tiger  ? Animal
Animal ? Animal
Banana ? Banana
Banana ? Fruit
Fruit  ? Fruit
Run Code Online (Sandbox Code Playgroud)

在C#4中,它支持某些接口的协变分配兼容性,第二组中的类型对之间存在赋值兼容性关系:

IE<Tiger>  ? IE<Tiger>
IE<Tiger>  ? IE<Animal>
IE<Animal> ? IE<Animal>
IE<Banana> ? IE<Banana>
IE<Banana> ? IE<Fruit>
IE<Fruit>  ? IE<Fruit>
Run Code Online (Sandbox Code Playgroud)

请注意,映射T ? IE<T> 保留了赋值兼容性的存在和方向.也就是说,如果X ? Y,那也是如此IE<X> ? IE<Y>.

如果我们在胖箭的两侧有两个东西,那么我们可以用相应的细箭头右侧的东西替换两侧.

具有关于特定关系的该属性的映射称为"协变映射".这应该是有道理的:可以在需要动物序列的地方使用一系列老虎,但事实恰恰相反.在需要一系列老虎的情况下,不一定能使用一系列动物.

这是协方差.现在考虑所有类型集的这个子集:

{ IComparable<Tiger>, 
  IComparable<Animal>, 
  IComparable<Fruit>, 
  IComparable<Banana> }
Run Code Online (Sandbox Code Playgroud)

现在我们有从第一组到第三组的映射T ? IC<T>.

在C#4中:

IC<Tiger>  ? IC<Tiger>
IC<Animal> ? IC<Tiger>     Backwards!
IC<Animal> ? IC<Animal>
IC<Banana> ? IC<Banana>
IC<Fruit>  ? IC<Banana>     Backwards!
IC<Fruit>  ? IC<Fruit>
Run Code Online (Sandbox Code Playgroud)

也就是说,映射T ? IC<T>已经保留了存在,但反转的方向分配的兼容性.那就是,如果X ? Y,那么IC<X> ? IC<Y>.

其中A映射保留但反转的关系被称为逆变映射.

同样,这应该是明确正确的.可以比较两只动物的装置也可以比较两只老虎,但是可以比较两只老虎的装置不一定能比较任何两只动物.

这就是C#4中协方差和逆变的区别.协方差保留了可分配性的方向.逆变会逆转它.

  • 对于像我这样的人来说,最好添加一些示例,说明什么是非协变性,什么是非逆变性,什么不是两者. (3认同)
  • @Bargitta:这非常相似。区别在于C#使用*确定站点差异*,而Java使用*调用站点差异*。因此,变化的方式是相同的,但是开发人员说“我需要将此作为变体”的地方不同。顺便说一句,两种语言的功能部分是由同一个人设计的! (2认同)
  • @AshishNegi:将箭头读为“可以用作”。“可以比较动物的东西可以用作可以比较老虎的东西”。现在有意义吗? (2认同)
  • @AshishNegi:不,那不对。**IEnumerable 是协变的,因为 T 只出现在 IEnumerable 的方法的返回值中。** 而 IComparable 是逆变的,因为 **T 只作为 IComparable 的方法的形式参数出现**。 (2认同)
  • @AshishNegi:您想考虑构成这些关系的“逻辑原因”。*为什么*我们可以安全地将“ IEnumerable &lt;Tiger&gt;”转换为“ IEnumerable &lt;Animal&gt;”?因为没有办法将长颈鹿“输入”到“ IEnumerable &lt;Animal&gt;”中。为什么我们可以将“ IComparable &lt;Animal&gt;”转换为“ IComparable &lt;Tiger&gt;”?因为没有办法从IComparable &lt;Animal&gt;中“取出”长颈鹿。说得通? (2认同)

Jon*_*eet 107

举例可能是最容易的 - 这当然是我记得它们的方式.

协方差

典型的例子:IEnumerable<out T>,Func<out T>

您可以转换IEnumerable<string>IEnumerable<object>,或转换Func<string>Func<object>.值只来自这些对象.

它的工作原理是因为如果你只是从API中获取值,并且它将返回特定的东西(比如string),你可以将返回的值视为更通用的类型(如object).

逆变

典型的例子:IComparer<in T>,Action<in T>

你可以转换IComparer<object>IComparer<string>,或转换Action<object>Action<string>; 值只会进入这些对象.

这一次它起作用,因为如果API期望一般(如object),你可以给它更具体的东西(如string).

更普遍

如果你有一个接口,IFoo<T>它可以是协变的T(即声明它IFoo<out T>好像T只用在接口内的输出位置(例如返回类型).它可以是逆变的T(即IFoo<in T>)T只用于输入位置(例如参数类型).

它可能会让人感到困惑,因为"输出位置"并不像它听起来那么简单 - 类型的参数Action<T>仍然只T在输出位置使用 - Action<T>如果你看到我的意思,那么它的回转是相反的.这是在该值可以从方法的实现通过"输出" 来电者的代码,就像一个返回值可以.通常这种事情不会出现,幸运的是:)

  • 对于我未来的自己,谁会再次回到这个极好的答案以重新学习差异,这是您想要的行:_“ [协方差]有效,因为如果您只是从API中获取值,并且它会返回特定的内容(例如字符串),您可以将该返回值视为更一般的类型(例如对象)。“ _ (2认同)

Nic*_*ico 15

我希望我的帖子有助于获得与该主题无关的语言.

对于我们的内部培训,我使用了精彩的书籍"Smalltalk,Objects and Design(Chamond Liu)",并重述了以下示例.

"一致性"是什么意思?我们的想法是设计具有高度可替换类型的类型安全类型层次结构.如果您使用静态类型语言,获得此一致性的关键是基于子类型的一致性.(我们将在这里高层讨论Liskov替换原则(LSP).)

实际例子(C#中的伪代码/无效):

  • 协方差:让我们假设那些用"静态"打字"持续"产卵的鸟:如果鸟类产卵,鸟类的子类型不会产生蛋的亚型吗?例如Duck类型放置DuckEgg,然后给出一致性.为什么这一致?因为在这样的表达式中:Egg anEgg = aBird.Lay();引用aBird可以合法地用Bird或Duck实例代替.我们说返回类型与类型协变,其中定义了Lay().子类型的覆盖可以返回更专用的类型.>"他们提供更多."

  • 逆变:让我们假设钢琴演奏家可以"静态地"演奏钢琴:如果钢琴家演奏钢琴,她能演奏GrandPiano吗?是不是宁愿Virtuoso演奏GrandPiano?(警告;有一个扭曲!)这是不一致的!因为在这样的表达中:aPiano.Play(aPianist);aPiano不能被钢琴或GrandPiano实例合法替代!GrandPiano只能由Virtuoso演奏,钢琴家太一般了!GrandPianos必须可以通过更一般的类型播放,然后播放是一致的.我们说参数类型与类型是逆变的,其中定义了Play().子类型的覆盖可以接受更通用的类型.>"他们需要更少."

回到C#:
由于C#基本上是一种静态类型语言,因此必须明确标记类型接口的"位置"(应该是共变或逆变)(例如参数和返回类型),以保证该类型的一致使用/开发,使LSP工作正常.在动态类型语言中,LSP一致性通常不是问题,换句话说,如果您只在类型中使用类型动态,则可以完全摆脱.Net接口和委托上的共变和逆变"标记". - 但这不是C#中最好的解决方案(你不应该在公共接口中使用动态).

回到理论:
所描述的一致性(协变返回类型/逆变参数类型)是理论上的理想(由Emerald和POOL-1语言支持).一些oop语言(例如Eiffel)决定应用另一种类型的一致性,尤其是.还有协变参数类型,因为它比理论理想更能描述现实.在静态类型语言中,通常必须通过应用诸如"双调度"和"访问者"之类的设计模式来实现期望的一致性.其他语言提供所谓的"多次调度"或多种方法(这基本上是在运行时选择函数重载,例如使用CLOS)或通过使用动态类型获得所需的效果.


wog*_*les 6

转换器委托帮助我理解差异。

delegate TOutput Converter<in TInput, out TOutput>(TInput input);
Run Code Online (Sandbox Code Playgroud)

TOutput表示协方差,其中方法返回更具体的类型

TInput表示方法传递一个不太具体的类型的逆变

public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }

public static Poodle ConvertDogToPoodle(Dog dog)
{
    return new Poodle() { Name = dog.Name };
}

List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
Run Code Online (Sandbox Code Playgroud)


Vad*_*imV 6

Co 和 Contra 方差是非常合乎逻辑的事情。语言类型系统迫使我们支持现实生活中的逻辑。通过例子很容易理解。

协方差

例如,您想买一朵花,而您所在的城市有两家花店:玫瑰花店和雏菊花店。

如果你问别人“花店在哪里?” 有人告诉你玫瑰店在哪里,可以吗?是的,因为玫瑰是一朵花,如果你想买一朵花,你可以买一朵玫瑰。如果有人回复您菊花店的地址,这同样适用。

这是协方差的示例:您可以强制转换A<C>A<B>,其中C是 的子类B,如果A生成泛型值(作为函数的结果返回)。协方差是关于生产者的,这就是 C# 使用关键字out进行协方差的原因。

类型:

class Flower {  }
class Rose: Flower { }
class Daisy: Flower { }

interface FlowerShop<out T> where T: Flower {
    T getFlower();
}

class RoseShop: FlowerShop<Rose> {
    public Rose getFlower() {
        return new Rose();
    }
}

class DaisyShop: FlowerShop<Daisy> {
    public Daisy getFlower() {
        return new Daisy();
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是“花店在哪里?”,答案是“玫瑰店在那里”:

static FlowerShop<Flower> tellMeShopAddress() {
    return new RoseShop();
}
Run Code Online (Sandbox Code Playgroud)

逆变

例如,你想送一朵花给你的女朋友,而你的女朋友喜欢任何花。你能把她当成一个爱玫瑰的人,还是一个爱雏菊的人?是的,因为如果她喜欢任何花,她就会喜欢玫瑰和雏菊。

这是逆变的一个示例:您可以强制转换A<B>A<C>,其中C是 的子类B,如果使用A通用值。逆变是关于消费者的,这就是 C# 使用关键字in进行逆变的原因。

类型:

interface PrettyGirl<in TFavoriteFlower> where TFavoriteFlower: Flower {
    void takeGift(TFavoriteFlower flower);
}

class AnyFlowerLover: PrettyGirl<Flower> {
    public void takeGift(Flower flower) {
        Console.WriteLine("I like all flowers!");
    }
}
Run Code Online (Sandbox Code Playgroud)

你把爱任何花的女朋友当成爱玫瑰的人,送她一朵玫瑰:

PrettyGirl<Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());
Run Code Online (Sandbox Code Playgroud)

链接