.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.0Visual 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;

        }

 

      警告:

       在实际开发中别写这样的代码,如果你这么做了,我担保你一定会被需要维护你代码的同事痛扁一顿!

 

         依据上述原则,你完全可以使用inout关键字定义支持“Covariance”“Contravariance”特性的泛型接口与泛型委托。

    不过,如果没有特殊需求,你还是直接用基类库中的现有接口和委托就行了,不要滥用“Covariance”“Contravariance”特性。

   

         对了,如果一个泛型接口(或泛型委托)声明为支持“Covariance”或“Contravariance”特性,我们就将它们统称为“variant”的泛型接口(或泛型委托)。

 

         事实上,CovarianceContravariance不仅适用于泛型接口和泛型委托同样适用于非泛型的委托

 

         例如,以下代码定义了一个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 = "您按了鼠标上的键";

        }

 

         示例程序的奇特之处在于,此函数可以直接挂接到按钮的KeyDownMouseClick事件上!

 

    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

加支付宝好友偷能量挖...


评论(0)网络
阅读(111)喜欢(0)Asp.Net/C#/WCF