.NET 4.0 “Covariance”和“Contravariance”趣话

面向对象的程序中,我们知道基类变量可以引用子类对象,比如 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:

.NET 4.0 “Covariance”和“Contravariance”趣话_第1张图片

 

示例程序定义了一个键盘和鼠标事件响应函数:

 

    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 事件的参数拥有以下继承关系:

.NET 4.0 “Covariance”和“Contravariance”趣话_第2张图片

 

 

再来看一下 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 ”应该翻译为协变和逆变

你可能感兴趣的:(.net,object,Class,action,interface,2010)