zoukankan      html  css  js  c++  java
  • 协变与逆变详细解读

    协变与逆变是.Net4.0新加入的概念,我看了很多博客文章,可能是我悟性比较差,感觉没有完全讲明白,自己研究了一天终于搞懂了,特此记录一下。

    一、简单理解协变和逆变

    //协变:子类对象(引用)赋值给父类变量(引用)
    object obj = null;
    string str = "";
    obj = str;
    //逆变:父类对象(引用)赋值给子类变量(引用)
    object obj = null;
    string str = "";
    str = obj;
    /*
    str = obj;这段代码大家会发现是错误的,这个赋值是基本操作都是错误的,
    那又如何实现逆变这种逆反的赋值操作呢?实际上逆变根本不是逆向将父类
    赋给子类的,我慢慢解释...
    */

    二、真正的协变和逆变

    概念:

    1、以前的泛型系统(或者说没有in/out关键字时),是不能“变”的,无论是“逆”还是“顺(协)”。
    2、当前仅支持接口和委托的逆变与协变 ,不支持类和方法。但数组也有协变性。
    3、值类型不参与逆变与协变。

    协变:Foo<ParentClass> = Foo<ChildClass>

    public class TestOut<T> where T : new()
    {
        /*
         * 关键字out因为协变的类型T只能作为输出参数使用,而不能作为输入参数
         */
        //*****[协变]泛型委托Demo*****
        //1、创建泛型委托
        public delegate T MyFunA<T>();     //默认不支持协变与逆变
        public delegate T MyFunB<out T>(); //设置支持协变
        //public delegate void MyFunC<out T>(T param);//错误,协变类型只能“出”不能“入”
        //2、创建委托变量
        public MyFunA<object> FunAObject = null;
        public MyFunA<string> FunAString = null;
        public MyFunB<object> FunBObject = null;
        public MyFunB<string> FunBString = null;
        public MyFunB<int> FunBInt = null;
        //3、验证结果
        public void TestFun()
        {
            //FunAObject = FunAString;//错误,不可用协变
            FunBObject = FunBString;//正确,可用协变可以完成子类string到父类object的转换
            //FunBObject = FunBInt;   //错误,值类型不参与协变
        }
    
        //*****[协变]泛型接口Demo*****
        //1、创建泛型接口
        public interface IMyInterfaceA<T> { }       //默认不支持协变与逆变
        public interface IMyInterfaceB<out T> { }   //设置支持协变
        //public interface IMyInterfaceC<out T>
        //{
        //    void Test(T param);//错误,协变只能“出”不能“入”
        //}
        //2、创建接口变量
        public IMyInterfaceA<object> interAObject = null;
        public IMyInterfaceA<string> interAString = null;
        public IMyInterfaceB<object> interBObject = null;
        public IMyInterfaceB<string> interBString = null;
        public IMyInterfaceB<int> interBInt = null;
        //3、验证结果
        public void TestInterface()
        {
            //interAObject = interAString;    //错误,不可用协变
            interBObject = interBString;    //正确,可用协变可以完成子类string到父类object的转换
            //interBObject = interBInt;       //错误,值类型不参与协变
        }
    }

    逆变:Foo<ChildClass> = Foo<ParentClass>

    public class TestIn<T> where T : new()
    {
        /*
         * 关键字in因为逆变的类型只能作为输入参数,而不能作为输出参数
         */
        //*****[逆变]泛型委托*****
        //1、创建泛型委托
        public delegate void MyActionA<T>(T param);     //默认不支持协变与逆变
        public delegate void MyActionB<in T>(T param);  //设置支持逆变
        //public delegate T MyActionC<in T>();//错误,逆变只能“入”不能“出”
        //2、创建委托变量
        public MyActionA<object> ActionAObject = null;
        public MyActionA<string> ActionAString = null;
        public MyActionB<object> ActionBObject = null;
        public MyActionB<string> ActionBString = null;
        public MyActionB<int> ActionBInt = null;
        //3、验证结果
        public void TestAction()
        {
            //ActionAString = ActionAObject;  //错误,不可用逆变
            ActionBString = ActionBObject;  //正确,可用逆变可以完成从父类object到子类string的转换
            //ActionBInt = ActionBObject;     //错误,值类型不参与逆变
        }
    
        //*****[逆变]泛型接口*****
        //1、创建泛型接口
        public interface IMyInterfaceA<T> { }     //默认不支持协变与逆变
        public interface IMyInterfaceB<in T> { }  //设置支持逆变
        //public interface IMyInterfaceC<in T>
        //{
        //    T Test();//错误,逆变只能“入”不能“出”
        //}
        //2、创建接口变量
        public IMyInterfaceA<object> interAObject = null;
        public IMyInterfaceA<string> interAString = null;
        public IMyInterfaceB<object> interBObject = null;
        public IMyInterfaceB<string> interBString = null;
        public IMyInterfaceB<int> interBInt = null;
        //3、验证结果
        public void TestInterface()
        {
            //interAString = interAObject;    //错误,不可用逆变
            interBString = interBObject;    //正确,可用你变可以完成从父类object到子类string的转换
            //interBInt = interBObject;       //错误,值类型不参与逆变
        }
    }

    三、解析协变与逆变(协变是顺序的,逆变并不是逆反的)

    这里就是我看别人的博客没有看懂的地方,研究时从这里卡住了半天,想通后发现豁然开朗,现在分享出来

    先创建一个协变接口,一个逆变接口

    //*****协变接口*****
    public interface ITestA<out T>
    {
        T Test();
    }
    public class TestA<T> : ITestA<T>
    {
        public T Test()
        {
            //do something...
            return default(T);
        }
    }
    //*****逆变接口*****
    public interface ITestB<in T>
    {
        void Test(T p);
    }
    public class TestB<T> : ITestB<T>
    {
        public void Test(T p)
        {
            //do something...
        }
    }

    协变解析:

    internal class Program
    {
        private static void Main(string[] args)
        {
            //写法一
            ITestA<object> testA = new TestA<string>();
            object obj = testA.Test();
            //写法二
            ITestA<object> testA1 = null;
            ITestA<string> testA2 = null;
            testA1 = testA2;
            obj = testA1.Test();
    
            /*
            执行步骤如下:
            //先调用父类函数
            public object ITestA<object>.Test()
            {
                //发现父类函数为接口,函数体由子类实现,所以...
                //再调用子类函数
                public string ITestA<string>.Test()
                {
                    //do something...   
                }
                //父类函数调用子类函数,子类函数向外return返回值,由string类型传至object类型
            }
            */
            //协变“可出不可入”因为由子类函数向父类函数返回值,子类类型小,父类类型大,所以可以进行安全转换
        }
    }

    别的博客中看到以上解释,没看明白,后来才懂,他的意思是:协变时是子类向父类返回值,值类型是由子到父,可以安全转换!
    [原式就是主观应该调用的方式,我想调用子类的这个函数]
    [变式就是实际运行时调用的方式,先调用父类函数再由父类函数调用子类函数]

    //ITest1<object> = ITest1<string>
    //原式(子类):string ITest1<string>.Test()
    ////变式(父类):object ITest1<object>.Test()

    逆变解析:

    internal class Program
    {
        private static void Main(string[] args)
        {
            //写法一
            ITestB<string> testB = new TestB<object>();
            testB.Test("");
            //写法二
            ITestB<string> testB1 = null;
            ITestB<object> testB2 = null;
            testB1 = testB2;
            testB1.Test("");
    
            /*
            执行步骤如下:
            //先调用父类函数
            public void ITestB<string>.Test(string param)
            {
                //发现父类函数为接口,函数体由子类实现,所以...
                //再调用子类函数
                public void ITestB<object>.Test(object param)
                {
                    //do something...   
                }
                //父类函数调用子类函数,并向子类函数传递参数由string类型传至object类型
            }
            */
            //逆变“可入不可出”因为由父类函数向子类函数传递参数,父类类型小,子类类型大,所以可以进行安全转换
        }
    }

    别的博客中看到以上解释,没看明白,后来才懂,他的意思是:逆变时是父类向子类传参数值,值类型是由子到父,可以安全转换!
    [原式就是主观应该调用的方式,我想调用子类的这个函数]
    [变式就是实际运行时调用的方式,先调用父类函数再由父类函数调用子类函数]

    //ITest2<string> = ITest2<object>
    //原式(子类):void ITest2<object>.Test(object param)
    ////变式(父类):void ITest2<string>.Test(string param)

    调用执行步骤:
    ITestA<object> testA = new TestA<string>();
    object obj = testA.Test();
    ITestB<string> testB = new TestB<object>();
    testB.Test("");
    父类变量(引用)调用方法,实际上执行步骤如下:
    1、调用父类自己的方法
    2、被告知方法体由子类实现
    3、父类去调用子类方法
    4、【逆变】发现子类方法有参,于是父类传递自己的参数(类型string)到子类(类型object),可以安全转换
    5、子类执行方法体功能
    6、【协变】将执行的返回值返回给父类
    7、【协变】父类接收子类方法返回值,返回值类型为子类的
    8、【协变】继续向上返回,发现返回值类型不一样(类型string),所以转为父类方法的类型返回(类型object),可以安全转换

    所以这就是为什么【协变只能返回值】,而【逆变只能传递值】,实际协变逆变并没有父类型转子类型的过程,都是使用的子类型转父类型的安全转换

    应用场景:微软提倡只要是泛型的接口或者委托都希望使用协变逆变,RedSharper也会有相应的提示,这样做也可以增加【函数传入参数值、【函数返回值】的扩展性,何乐而不为呢~

  • 相关阅读:
    17.1.2.1 Advantages and Disadvantages of Statement-Based and Row-Based Replication
    17.1.2 Replication Formats
    Setting the Master Configuration on the Slave
    17.1.1.9 Introducing Additional Slaves to an Existing Replication Environment
    17.1.1.8 Setting Up Replication with Existing Data
    17.1.1.7 Setting Up Replication with New Master and Slaves
    17.1.1.6 Creating a Data Snapshot Using Raw Data Files
    列出display的值,并说明它们的作用
    CSS设置DIV居中
    CSS选择符有哪些?哪些属性可以继承?优先级算法如何计算?
  • 原文地址:https://www.cnblogs.com/taiyonghai/p/6524910.html
Copyright © 2011-2022 走看看