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产鸭蛋,那么一致性是给定的。为什么一致?因为在这样的表达式中:
    Egg anEgg=aBird.lay();
    引用aBird可以合法地由Bird或Duck实例替换。我们说返回类型与定义Lay()的类型是协变的。子类型的重写可能返回更专门的类型。=>“它们提供更多。”

  • 相反:让我们假设钢琴家可以通过静态打字“始终如一地”弹奏钢琴:如果钢琴家弹奏钢琴,她能弹奏一架大钢琴吗?难道一位演奏家不愿意弹奏一架大钢琴吗?(请注意,这是一个转折!)这是不一致的!因为在这样的表达中:
    aPiano.play(aPianist)
    aPiano不能被钢琴或大钢琴实例合法替代!大钢琴只能由演奏家演奏,钢琴家太普通了!大钢琴必须可以由更普通的类型演奏,那么演奏是一致的。我们说参数类型与类型相反,其中play()已定义。子类型的重写可以接受更一般化的类型。=>“它们需要更少。”

回到C:
因为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());