• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
学习笔记
Misaka的学习笔记
博客园    首页    新随笔    联系   管理    订阅  订阅
c#学习笔记----------------------------协变和逆变
  • 协变和逆变

  • 协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。 协变保留分配兼容性,逆变则与之相反。

  • 协变

  • 以下代码演示支持协变与不支持协变的泛型和数组的区别
  •         //泛型委托
            public delegate T MyFuncA<T>();//不支持逆变与协变
            public delegate T MyFuncB<out T>();//支持协变
            //泛型接口
            public interface IFlyA<T>
            { }//不支持逆变与协变
            public interface IFlyB<out T>
            { }//支持协变
            private static void Main(string[] args)
            {
                string str = "string";
                object obj = str;
                Console.WriteLine("c#中可以安全地把str的引用赋给obj引用");
                MyFuncA<object> funcAObject = null;
                MyFuncA<string> funcAString = null;
                MyFuncB<object> funcBObject = null;
                MyFuncB<string> funcBString = null;
                MyFuncB<int> funcBInt = null;
                //funcAObject=funcAString;//编译失败,MyFuncA不支持逆变与协变
                funcBObject = funcBString;//编译成功,MyFuncB支持协变
                //funcBObject=funcBInt;//编译失败,值类型不参与协变
                Console.WriteLine("支持协变的泛型,可以安全地把funcBObject的引用赋给funcBObject引用");
                //接口
    
                IFlyA<object> flyAObject = null;
                IFlyA<string> flyAString = null;
                IFlyB<object> flyBObject = null;
                IFlyB<string> flyBString = null;
                IFlyB<int> flyBInt = null;
    
                //flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变
                flyBObject = flyBString;//编译成功,IFlyB支持协变
                //flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变
                Console.WriteLine("支持协变的泛型接口同理,可以安全地把flyBString的引用赋给flyBObject引用");
                //数组:
                string[] strings = new string[] { "string" };
                object[] objects = strings;
                Console.WriteLine("数组可以安全地把strings的引用赋给objects引用");
                Console.ReadLine();
            }

     关于协变的定义:

  • 协变就是对具体成员的输出参数进行一次类型转换,且类型转换的准则是 “里氏替换原则”。
  • 逆变

  •    //泛型委托
       public delegate T MyFuncA<T>();//不支持逆变与协变
    
       public delegate T MyFuncB<out T>();//支持协变
    
       public delegate void MyActionA<T>(T param);//不支持逆变与协变
    
       public delegate void MyActionB<in T>(T param);//支持逆变
    
       //泛型接口
       public interface IFlyA<T>
       { }//不支持逆变与协变
    
       public interface IFlyB<out T>
       { }//支持协变
    
       public interface IPlayA<T>
       { }//不支持逆变与协变
    
       public interface IPlayB<in T>
       { }//支持逆变
    
       private static void Main(string[] args)
       {
           string str = "string";
           object obj = str;
           Console.WriteLine("c#中可以安全地把str的引用赋给obj引用");
           MyFuncA<object> funcAObject = null;
           MyFuncA<string> funcAString = null;
           MyFuncB<object> funcBObject = null;
           MyFuncB<string> funcBString = null;
           MyFuncB<int> funcBInt = null;
           //funcAObject=funcAString;//编译失败,MyFuncA不支持逆变与协变
           funcBObject = funcBString;//编译成功,MyFuncB支持协变
           //funcBObject=funcBInt;//编译失败,值类型不参与协变
           Console.WriteLine("支持协变的泛型,可以安全地把funcBObject的引用赋给funcBObject引用");
           //接口
    
           IFlyA<object> flyAObject = null;
           IFlyA<string> flyAString = null;
           IFlyB<object> flyBObject = null;
           IFlyB<string> flyBString = null;
           IFlyB<int> flyBInt = null;
    
           //flyAObject = flyAString;//编译失败,IFlyA不支持逆变与协变
           flyBObject = flyBString;//编译成功,IFlyB支持协变
           //flyBObject = flyBInt;//编译失败,值类型不参与协变或逆变
           Console.WriteLine("支持协变的泛型接口同理,可以安全地把flyBString的引用赋给flyBObject引用");
           //数组:
           string[] strings = new string[] { "string" };
           object[] objects = strings;
           Console.WriteLine("数组可以安全地把strings的引用赋给objects引用");
           Console.ReadLine();
           object[] array = new String[10];
    
           //
           MyActionA<object> actionAObject = null;
           MyActionA<string> actionAString = null;
           MyActionB<object> actionBObject = null;
           MyActionB<string> actionBString = null;
           actionAString = actionAObject;//MyActionA不支持逆变与协变,编译失败
           actionBString = actionBObject;//变了,逆变
    
           IPlayA<object> playAObject = null;
           IPlayA<string> playAString = null;
           IPlayB<object> playBObject = null;
           IPlayB<string> playBString = null;
           playAString = playAObject;//IPlayA不支持逆变与协变,编译失败
           playBString = playBObject;//变了,逆变
       }

    关于逆变的定义

  • 逆变就是对具体成员的输入参数进行一次类型转换,且类型转换的准则是"里氏替换原则"。
  • 总结

  • 总结有以下几点:

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

    那么in/out是什么意思呢?为什么加了它们就有了“变”的能力,是不是我们定义泛型委托或者接口都应该添加它们呢?

    原来,在泛型参数上添加了in关键字作为泛型修饰符的话,那么那个泛型参数就只能用作方法的输入参数,或者只写属性的参数,不能作为方法返回值等,总之就是只能是“入”,不能出。out关键字反之。

  •  

     

  •  

    自问自答

  • 1、协变、逆变 为什么只能针对泛型接口或者委托?而不能针对泛型类?

     

    因为它们都只能定义方法成员(接口不能定义字段),而方法成员在创建对象的时候是不涉及到对象内存分配的,所以它们是类型(内存)安全的。

     

    为什么不针对泛型?因为泛型类是模板类,而类成员是包含字段的,不同类型的字段是影响对象内存分配的,没有派生关系的类型它们是不兼容的,也是内存不安全的。

     

    2、协变、逆变 为什么是类型安全的?

     

    本质上是里氏替换原则,由里氏替换原则可知:派生程度小的是派生程度大的子集,所以子类替换父类的位置整个程序功能都不会发生改变。

     

    3、官方对 协变、逆变 的定义现在是否能看懂?

     

    上面看懂了,官方定义肯定也是没问题的。派生程度小可以理解为基类,派生程度大可以理解为子类或派生类,至于为什么用程度这个词,是因为继承链的深度是没限制的。

     

    4、为什么 in 、out 只能是单向的(输入或输出)?

     

    因为若类型参数同时为输入参数和输出参数,则必然会有一种转换不符合里氏替换原则,即将父类型的变量赋值给子类型的变量,这是不安全的所以需要明确指定 in 或 out。

  •  

    参考文章链接

  • https://www.cnblogs.com/lemontea/archive/2013/02/17/2915065.html 

  • https://www.cnblogs.com/JingShaoHui/p/14321835.html
  • https://zhuanlan.zhihu.com/p/629649432
  • https://blog.csdn.net/poorkick/article/details/114006840
posted on 2024-01-15 14:44  我们打工人  阅读(50)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3