C# 协方差和;反向方差
我很难理解协方差和反方差之间的区别 举个例子可能是最容易的——我肯定就是这样记住它们的 协方差 典型示例:C# 协方差和;反向方差,c#,c#-4.0,covariance,contravariance,C#,C# 4.0,Covariance,Contravariance,我很难理解协方差和反方差之间的区别 举个例子可能是最容易的——我肯定就是这样记住它们的 协方差 典型示例:IEnumerable,Func 您可以从IEnumerable转换为IEnumerable,或将Func转换为Func。值仅来自这些对象 它之所以有效,是因为如果您只从API中提取值,并且它将返回特定的内容(如字符串),那么您可以将返回的值视为更一般的类型(如对象) 对冲 典型示例:IComparer,Action 您可以从IComparer转换为IComparer,或将Action转换为
IEnumerable
,Func
您可以从IEnumerable
转换为IEnumerable
,或将Func
转换为Func
。值仅来自这些对象
它之所以有效,是因为如果您只从API中提取值,并且它将返回特定的内容(如字符串
),那么您可以将返回的值视为更一般的类型(如对象
)
对冲
典型示例:IComparer
,Action
您可以从IComparer
转换为IComparer
,或将Action
转换为Action
;值只进入这些对象
这一次它可以工作,因为如果API需要一些通用的东西(比如对象
),那么可以给它一些更具体的东西(比如字符串
)
更一般地说
如果您有一个接口IFoo
,它可以在T
中协变(即,如果T
仅用于接口内的一个输出位置(例如返回类型),则将其声明为IFoo
。如果T
仅用于输入位置,它可以在T
中逆变(即IFoo
)(例如,参数类型)
这可能会让人困惑,因为“输出位置”并不像听起来那么简单-类型为Action
的参数仍然只在输出位置使用t
-如果你明白我的意思的话,Action
的反差会把它转过来。这是一个“输出”因为值可以从方法的实现传递到调用方的代码,就像返回值一样。幸运的是,通常不会出现这种情况:)问题是“协方差和逆变换之间的区别是什么?”
协方差和逆变是映射函数的属性,映射函数将集合的一个成员与另一个成员相关联。更具体地说,映射可以是该集合上关系的协变或逆变
考虑所有C#类型集合的以下两个子集。首先:
{ Animal,
Tiger,
Fruit,
Banana }.
第二,这个明确相关的集合:
{ IEnumerable<Animal>,
IEnumerable<Tiger>,
IEnumerable<Fruit>,
IEnumerable<Banana> }
在支持某些接口的协变分配兼容性的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>
现在我们有了从第一个集合到第三个集合的映射→ 集成电路
在C#4中:
IC⇒ 集成电路
集成电路⇒ 倒转!
集成电路⇒ 集成电路
集成电路⇒ 集成电路
集成电路⇒ 倒转!
集成电路⇒ 集成电路
也就是说,映射T→ IC
保留了存在,但改变了分配兼容性的方向。也就是说,如果X⇒ Y,然后是IC⇐ 集成电路
保留但反转关系的映射称为逆变映射
同样,这应该是明确正确的。一个能比较两种动物的装置也能比较两只老虎,但一个能比较两只老虎的装置不一定能比较任何两种动物
这就是C#4中协方差和逆变的区别。协方差保留了可分配性的方向。逆变反了它。我希望我的文章能帮助人们对这个话题有一个语言不可知的观点 在我们的内部培训中,我学习了一本很棒的书“Smalltalk,Objects and Design(Chamond Liu)”,我重新表述了以下示例 “一致性”是什么意思?其思想是用高度可替换的类型设计类型安全的类型层次结构。如果您使用静态类型语言,那么获得这种一致性的关键是基于子类型的一致性。(我们将在较高的层次上讨论Liskov替换原则(LSP)) 实际示例(伪代码/C#中无效):
- 协方差:让我们假设鸟类与静态类型“一致”产卵:如果类型Bird产卵,Bird的子类型不会产卵吗?例如,类型Duck产鸭蛋,那么一致性是给定的。为什么一致?因为在这样的表达式中:
引用aBird可以合法地由Bird或Duck实例替换。我们说返回类型与定义Lay()的类型是协变的。子类型的重写可能返回更专门的类型。=>“它们提供更多。”Egg anEgg=aBird.lay();
- 相反:让我们假设钢琴家可以通过静态打字“始终如一地”弹奏钢琴:如果钢琴家弹奏钢琴,她能弹奏一架大钢琴吗?难道一位演奏家不愿意弹奏一架大钢琴吗?(请注意,这是一个转折!)这是不一致的!因为在这样的表达中:
aPiano不能被钢琴或大钢琴实例合法替代!大钢琴只能由演奏家演奏,钢琴家太普通了!大钢琴必须可以由更普通的类型演奏,那么演奏是一致的。我们说参数类型与类型相反,其中play()已定义。子类型的重写可以接受更一般化的类型。=>“它们需要更少。”aPiano.play(aPianist)
因为C#基本上是一种静态类型语言,所以类型接口的“位置”应该是共变或逆变的(例如参数和返回类型),必须显式标记,以保证该类型的一致使用/开发,使LSP正常工作。在动态类型语言中,LSP一致性通常不是问题,换句话说,您可以完全摆脱共变和逆变“标记”在.Net接口和委托上,如果您仅在类型中使用类型dynamic。-但是
IE<Tiger> ⇒ IE<Tiger>
IE<Tiger> ⇒ IE<Animal>
IE<Animal> ⇒ IE<Animal>
IE<Banana> ⇒ IE<Banana>
IE<Banana> ⇒ IE<Fruit>
IE<Fruit> ⇒ IE<Fruit>
{ IComparable<Tiger>,
IComparable<Animal>,
IComparable<Fruit>,
IComparable<Banana> }
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>
delegate TOutput Converter<in TInput, out TOutput>(TInput input);
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();
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();
}
}
static FlowerShop<Flower> tellMeShopAddress() {
return new RoseShop();
}
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!");
}
}
PrettyGirl<Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());