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中协方差和逆变的区别.协方差保留了可分配性的方向.逆变会逆转它.
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>如果你看到我的意思,那么它的回转是相反的.这是在该值可以从方法的实现通过"输出" 向来电者的代码,就像一个返回值可以.通常这种事情不会出现,幸运的是:)
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)或通过使用动态类型获得所需的效果.
转换器委托帮助我理解差异。
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)
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)
| 归档时间: |
|
| 查看次数: |
27414 次 |
| 最近记录: |