Covariance and Contravariance In C#, Part Five: Higher Order Functions Hurt My Brain

本文讨论了C#中协变与逆变的概念,特别是在委托类型中的应用。通过实例解释了如何使委托返回类型协变及参数类型逆变,并探讨了在更高阶函数编程中使用协变参数的复杂情况。
 

Last time I discussed how we could in a hypothetical future version of C# allow delegate types to be covariant in their return type and contravariant in their formal parameter types. For example, we could have a contravariant action delegate:

delegate void Action< -A > (A a);

and then have

Action<Animal> action1 = (Animal a)=>{ Console.WriteLine(a.LatinName); };
Action<Giraffe> action2 = action1;

Because action2’s caller will always pass in something that action1 can handle.

Based on my discussion so far, I hope that you have a strong intuition that the normal, sensible use of variance is “stuff going ‘in’ may be contravariant, stuff going ‘out’ may be covariant”. Though I believe that would be the most common use of variance were we to enable this feature in a hypothetical future version of C#, the real situation would actually be rather more complicated than that. There is a situation where it is legal to use a covariant parameter in the formal parameter of a delegate. Doing so makes my brain hurt, but this also builds character, so here we go!

Suppose you want to do "higher order" functional programming. For example, perhaps you want to define a meta-action – a delegate which takes actions and does something with them:

delegate void Meta<A> (Action<A> action);

for example,

Meta<Mammal> meta1 = (Action<Mammal> action)=>{action(new Giraffe());};
// The next line is legal because Action<Animal> is smaller than Action<Mammal>;
// remember, Action is contravariant
meta1(action1);

So this Meta thing takes an Action on Mammals – say, action1 above, which prints the Latin name of any Animal, and therefore can do so to any Mammal – and then invokes that action on a new Giraffe.

Clearly the type parameter A is used in an input position in the definition of Meta<A>, so we should be able to make it contravariant, right? Suppose we did so. That would mean that this assignment would be legal:

Meta<Tiger> meta2 = meta1; // would be legal if Meta were contravariant in its parameter

But that means that this would then be legal:

Action<Tiger> action3 = tiger=>{ tiger.Growl(); };
meta2(action3);

Follow the logic through and you’ll see that we end up calling (new Giraffe()).Growl(), which is clearly a violation of both the type system and of nature’s laws.

Thus Meta<A> cannot be contravariant in A. It can however be covariant:

Meta<Animal> meta3 = meta1; // legal if Meta were covariant

Now everything works. meta3 takes an Action on Animals and passes a Giraffe to that Action. We’re all good.

Contravariance is tricky; the fact that it reverses the bigger/smaller relationship between types means that a type parameter used in a "doubly contravariant" position (being an input of Action, which is itself an input of Meta) becomes covariant. The second reversal undoes the first.

We do not expect that most people will use variance in this manner; rather, we expect that people will almost always use covariant parameters in output positions and contravariant parameters in input positions. As we’ll see a bit later in this series, whether this expectation is reasonable or not would influence the syntax we might choose were we to add variance to a hypothetical future version of C#.

Next time: we’ll leave delegates behind and talk about variance in interfaces.

 

转载于:https://www.cnblogs.com/i2001she1u/archive/2009/02/27/1399724.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值