面向对象的程序中,我们知道基类变量可以引用子类对象,比如 List<T> 派生自 IEnumerable<T> ,所以,以下这句绝无问题:
IEnumerable<Parent > P = new List<Parent >();
现在假设 Parent 类有一个子类,取名 Child 。
class Parent { }
class Child : Parent
{ }
请看以下“错误的”代码:
IEnumerable<Parent> P = new List<Child >();
虽然基类变量可以引用子类对象,但上述代码在 .NET 4.0 之前无法通过编译。
现在有趣的事情发生了,你会发现,同样的代码在 .NET 4.0 和 Visual Studio 2010 中则可以顺利通过编译。
这是怎么回事?
请看一下 IEnumerable<T > 的声明:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
这里面多了一个“神秘”的 out 关键字。正是因为它,才发生那些过去不可能发生的情况。
在 .NET 4.0 中,如果一个泛型接口(或泛型委托)的类型参数前有一个 out 关键字,那么,此类型参数“子类 / 父类对象通吃”。 这种特性 称为“Covariance ”。
再来看 .NET 基类库中的泛型委托 Action<T> 的定义:
public delegate void Action< in T>(T obj);
请注意其中有一个“ in ”关键字,由于前面看到了“ out ”的介绍,敏感的读者一定会估 计这个“ in ”里可能有点名堂,来看个例子。
以下代码定义了一个接收基类 Parent 对象的方法:
static void ParentFunc(Parent p)
{
// ……(代码略)
}
我们发现,在 .NET4.0 中,此方法可以传给一个以子类 Child 作为类型参数的 Action 委托 !
Action<Child > del = ParentFunc ;
由此我们知道,在 .NET 4.0 中,如果一个泛型委托(或泛型接口)的类型参数前有一个 in 关键字,那么,“定义为子类型的 in 类型参数可以接收对应位置的定义为父类型参数的方法” ,这句话实在是太别扭了,但请读者 原谅我的汉语水平。
这种特性被称为“Contravariance ”。
提示:
别问我“Covariance ”和“Contravariance ” 和这两个词如何翻译,我也不知道,大家等着中文MSDN 出来,看看微软的牛人们怎么将这两个词翻译 为汉语吧。
为便于记忆,可以总结为两句话:
类型参数前有“in ”的,基类可以传入 给子类,叫“Contravariance ”。
类型参数前有“out ”的,子类可以传出 给父类,叫“Covariance ”。
比较“变态”的是有些委托可以同时“ in ”和“ out ”,请看 .NET 基类库中的 Func<T,TResult> 的定义:
public delegate TResult Func<in T, out TResult>(T arg);
于是,我们可以写出以下完全正确但却让人“昏菜”的代码:
Func<Child, Parent> func = MyFunc;
其中, MyFunc 方法定义如下:
static Child MyFunc(Parent p)
{
return p as Child;
}
警告:
在实际开发中别写 这样的代码,如果你这么做了,我担保你一定会被需要维护你代码的同事痛扁一顿!
依据上述原则,你完全可以使用 in 和 out 关键字定义支持“ Covariance” 和 “Contravariance” 特性的泛型接口与泛型委托。
不过,如果没有特殊需求,你还是直接用基类库中的现有接口和委托就行了,不要滥用“Covariance” 和 “Contravariance” 特性。
对了,如果一个泛型接口(或泛 型委托)声明为支持“ Covariance ”或“ Contravariance” 特性,我们就将它们统称为“ variant ”的泛型接 口(或泛型委托)。
事实上, Covariance 和 Contravariance 不仅适用于泛型接口和泛型委托 , 同样适用于非泛型的委托 。
例如,以下代码定义了一个 MyDelegate 委托,注意它返回一个 Parent 对象:
public delegate Parent MyDelegate();
则我们可以写出以下代码:
MyDelegate del = delegate()
{
return new Child() ;
};
需要注意的是,上述使用“匿名 方法”给委托变量赋值仅适用于“ Covariance ”,如果要用于“ Contravariance ”,你必须老老实实地写一个独立的函数,再赋值给委托变量。
好了,来看一个“真正有点用”的实例吧。
请看一个“使用同一个函数同时 响应鼠标和键盘操作”的示例程序 ContravarianceExample:
示例程序定义了一个键盘和鼠标事件响应函数:
private void MultiHandler(object sender, System.EventArgs e)
{
if (e is KeyEventArgs )
lblInfo.Text = string.Format(" 您敲了 {0} 键 ",
(e as KeyEventArgs).KeyCode.ToString());
if(e is MouseEventArgs )
lblInfo.Text = " 您按了鼠标上的键 ";
}
示例程序的奇特之 处在于,此函数可以直接挂接到按钮的 KeyDown 和 MouseClick 事件上 !
btnTestCovariance.MouseClick += MultiHandler;
btnTestCovariance.KeyDown += MultiHandler;
让我们分析一下 “后台”到底发生了什么事情。
首先,我们注意到 MouseClick 事件和 KeyDown 事件的参数拥有以下继承关系:
再来看一下 KeyDown 事件和 MouseClick 事件的定义:
public event KeyEventHandler KeyDown;
public event MouseEventHandler MouseClick;
请注意两个事件其 实都是委托类型的变量。以下是两个事件委托的定义:
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
public delegate void MouseEventHandler(object sender, MouseEventArgs e)
可以看到,这两个委托的定 义,其参数都是 EventArgs 的子类。所以,依据 Contravariance 特性,它们可以接收参数定义为父类 EventArgs 的方法。这正是 MultiHandler 可以直接作为统一的事件响应函数挂接到键盘和鼠标事件的原因。
===================
下载示 例程序 (http://files.cnblogs.com/bitfan/ContravarianceExample.rar )
转载来至 http://blog.csdn.net/bitfan/archive/2010/01/25/5255425.aspx
提示:“Covariance ”和“Contravariance ”应该翻译为协变和逆变