简体   繁体   English

C#泛型委托中的协方差/协方差问题

[英]covariance/contravariance problem in C# generic delegate

In the below code there are two generic delegate declarations with covariance/contravariance : 在以下代码中,有两个具有协方差/相反方差的通用委托声明:

// wrong code since Delegate1 actually needs covariance
public delegate void Delegate1<in T>();
public delegate void Delegate2<in T>(Delegate1<T> d1);

to fix it, we can adjust Delegate1's declaration to covariance 要修复它,我们可以将Delegate1的声明调整为协方差

// ok
public delegate void Delegate1<out T>();
public delegate void Delegate2<in T>(Delegate1<T> d1);

but if I adjust " Delegate2<in T>(Delegate1<T> d1) " to " Delegate2<in T>(Delegate1<Delegate1<T>> d1) ", code below will be both OK(whether Delegate1 is covariance or contravariance) 但是如果我将“ Delegate2<in T>(Delegate1<T> d1) ”调整为“ Delegate2<in T>(Delegate1<Delegate1<T>> d1) ”,则下面的代码都可以(无论Delegate1是协方差还是协方差) )

// ok
public delegate void Delegate1<in T>();
public delegate void Delegate2<in T>(Delegate1<Delegate1<T>> d1);
// ok too
public delegate void Delegate1<out T>();
public delegate void Delegate2<in T>(Delegate1<Delegate1<T>> d1);

I'm not so sure about the reason ... 我不确定原因...

This question illustrates some interesting facts about contravariance and covariance. 这个问题说明了有关协方差和协方差的一些有趣事实。

There are two ways to understand these problems. 有两种方法可以了解这些问题。 The first is to look at it abstractly and just look at "what direction the arrows go". 首先是抽象地看它,而只是看“箭头的方向”。

Remember that "covariance" means that a transformation preserves the direction of the assignability arrow and "contravariance" means it is reversed. 请记住,“协方差”表示变换保留了可分配性箭头的方向,“协方差”表示将其反转。 That is, if A --> B means "An object of type A can be assigned to a variable of type B", then: 也就是说,如果A-> B表示“可以将类型A的对象分配给类型B的变量”,则:

Giraffe --> Animal
IEnumerable<Giraffe> --> IEnumerable<Animal>
IComparable<Giraffe> <-- IComparable<Animal>

Making a sequence preserves the direction of the arrow; 进行序列保留箭头的方向; it is "co-variant". 它是“协变量”。 "Co" meaning "going with" here. “ Co”在这里的意思是“一起去”。 Making a comparison reverses the direction, it is "contra", meaning "going against". 进行比较会使方向相反,即“相反”,表示“违背”。

This should make sense; 这应该是有道理的; a sequence of giraffes can be used where a sequence of animals is needed. 如果需要一系列动物,可以使用一系列长颈鹿。 And if you have a thing that can compare any animals, then it can compare any giraffes. 如果您拥有可以比较任何动物的东西,那么它就可以比较任何长颈鹿。

The way to understand why your last two program fragments are both legal is because in the case where you have two nested covariant types, you are saying "go the same direction, then go the same direction as that", which is the same as "go the same direction". 理解为什么最后两个程序片段都合法的原因是,在具有两个嵌套协变量类型的情况下,您说的是“朝着相同的方向,然后朝着相同的方向”,这与“走同一方向”。 When you nest two contravariant types, you are saying "go the opposite direction, then go the opposite direction as that", which is the same as "go the same direction"! 当您嵌套两个反变类型时,您说的是“朝相反的方向,然后朝那个相反的方向”,这与“朝相同的方向”一样! Contravariance reverses the direction of an arrow. 反转会反转箭头的方向。 Reversing the arrow twice turns it back the way it was facing originally! 两次反转箭头会将其恢复原来的状态!

But that is not how I like to understand these things. 但这不是我喜欢理解这些事情的方式。 Rather, I like to think about the question "what could go wrong if we did it the other way?" 相反,我想考虑一个问题:“如果我们反过来这样做会出什么问题呢?”

So let's look at your four cases and ask "what can go wrong"? 因此,让我们看一下您的四种情况,然后问“可能出什么问题”?

I'll make some small changes to your types. 我将对您的类型进行一些小的更改。

public delegate void D1<in T>(T t);
public delegate void D2<in T>(D1<T> d1t); // This is wrong.

Why is D2 wrong? 为什么D2错误? Well, what can go wrong if we allowed it? 好吧,如果我们允许,会出什么问题?

// This is a cage that can hold any animal.
AnimalCage cage = new AnimalCage(); 
// Let's make a delegate that inserts an animal into the cage.
D1<Animal> d1animal = (Animal a) => cage.InsertAnimal(a);
// Now lets use the same delegate to insert a tiger. That's fine!
D1<Tiger> d1tiger = d1animal;
d1tiger(new Tiger());

Now there is a tiger in cage, which is fine; 现在笼子里有只老虎,很好。 the cage can hold any animal. 笼子里可以放任何动物。

But now let's see how things go wrong with D2. 但是,现在让我们看看D2怎么出问题了。 Let's suppose that the declaration of D2 was legal. 让我们假设D2的声明是合法的。

// This line is fine; we're assigning D1<Animal> to D1<Tiger> 
// and it is contravariant.
D2<Animal> d2animal = (D1<Animal> d1a) => {d1tiger = d1a;}; 
// An aquarium can hold any fish.
Aquarium aquarium = new Aquarium();
// Let's make a delegate that puts a fish into an aquarium.
D1<Fish> d1fish = (Fish f) => aquarium.AddFish(f);
// This conversion is fine, because D2 is contravariant.
D2<Fish> d2fish = d2animal;
// D2<Fish> takes a D1<Fish> so we should be able to do this:
d2fish(d1fish);
// Lets put another tiger in the cage.
d1tiger(new Tiger());

OK, every line in that program was type safe . 好的, 该程序中的每一行都是safe类型 But trace through the logic. 但是要追溯逻辑。 What happened? 发生了什么? When we called d1tiger on the last line, what did it equal? 当我们在最后一行调用d1tiger时,它等于什么? Well, d2fish(d1fish) assigns d1fish to... d1tiger. 好吧,d2fish(d1fish)将d1fish分配给... d1tiger。 But d1tiger is typed as D1<Tiger> not D1<Fish> . 但是d1tiger键入为D1<Tiger>而不是D1<Fish> So we've assigned a value to a variable of the wrong type. 因此,我们为错误类型的变量分配了一个值。 Then what happened? 那怎么了 We called d1Tiger with a new tiger, and d1Tiger put a tiger into an aquarium! 我们用一只新老虎叫d1Tiger,而d1Tiger则将一只老虎放入了水族馆!

Every one of those lines was typesafe, but the program was not typesafe, so what should we conclude? 这些行中的每一行都是类型安全的,但是程序不是类型安全的,那么我们应该得出什么结论呢? The declaration of D2 was not typesafe . D2的声明不是类型安全的 And that's why the compiler gives you an error. 这就是为什么编译器会给您一个错误。

Based on this analysis we know that D2<in T>(D1<T>) has to be wrong. 基于此分析,我们知道D2<in T>(D1<T>)必须是错误的。

Exercise 1 : 练习1

delegate T D3<out T>();
delegate void D4<in T>(D3<T> d3t);

Go through the same logic I did, but this time, convince yourself that this never gives rise to a type system problem. 遵循与我相同的逻辑,但是这次,使自己确信,这绝不会引起类型系统问题。

Once you've got that down, then do the hard ones: 一旦记下了这些,就要做一些困难的事情:

Exercise 2 : Go through the logic again, but this time with 练习2 :再次讲解逻辑,但这一次

delegate void D5<in T>(D3<D3<T>> d3d3t);

Again, convince yourself that this is legal, and that this case is logically the same as Exercise 1. 再次,使自己确信这是合法的,并且此案例在逻辑上与练习1相同。

Exercise 3 : And the last, hardest one is: 练习3 :最后,最难的是:

delegate void D6<in T>(D1<D1<T>> d1d1t);

Convince yourself that this is legal because D1<D1<T>> reverses the arrow twice, and is therefore logically the same as Exercise 1. 说服自己这是合法的,因为D1<D1<T>>将箭头两次反转,因此在逻辑上与练习1相同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM