首页 > 编程语言 >c#学习笔记----------------------------协变和逆变

c#学习笔记----------------------------协变和逆变

时间:2024-01-15 14:58:27浏览次数:26  
标签:string c# 支持 ---------------------------- 逆变 协变 泛型 null

  • 协变和逆变

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

  • 协变

  • 以下代码演示支持协变与不支持协变的泛型和数组的区别
  •         //泛型委托
            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

标签:string,c#,支持,----------------------------,逆变,协变,泛型,null
From: https://www.cnblogs.com/misakayoucn/p/17961013

相关文章

  • Chrome 插件 V3 版本 Manifest.json 中的内容脚本(Content Scripts)解析
    内容脚本(ContentScripts)指定在用户打开某些网页时要使用的JavaScript或CSS文件。内容脚本是在网页环境中运行的文件。通过使用标准文档对象模型 (DOM),开发者能够读取浏览器所访问网页的详情、更改这些网页,并将信息传递给其父级扩展程序。一、内容脚本功能内容脚本在......
  • 最高法--实际施工人索要工程款时发包人欠付款项数额的相关证明应由发包人自行提交,发包
    (2021)最高法民再147号  固原佳和房地产开发有限公司、董维东等建设工程施工合同纠纷民事再审民事判决书再审申请人主张:佳和房地产公司申请再审请求:1.撤销二审判决;2.驳回董维东对佳和房地产公司的诉讼请求;3.案件受理费由董维东、杨占刚、得发建设公司承担。事实和理由:(一)二审判决......
  • 关于gunicorn与异步兼容性问题:AttributeError: module 'select' has no attribute 'ep
    关于gunicorn与异步兼容性问题:AttributeError:module'select'hasnoattribute'epoll'背景:介绍:  在使用gunicorn、Flask&flask-sockets部署,实现websocket协议中同类消息阻塞,不同类消息不阻塞场景。异常:[2024-01-1510:22:16+0800][31655][ERROR]Ex......
  • 桃李
    桃李 táolǐ[peachesandplums]桃花和李花;比喻栽培的后辈和所教的门生;喻人的青春年少;喻争荣斗艳、品格低下的小人庸人辞典解释桃李  táolǐ  ㄊㄠˊㄌㄧˇ  战国魏文侯的臣子子质,因罪北逃,而在简主面前抱怨,认为朝廷内外有一半的人是他栽培的,可是却与他作对,因......
  • docker安装软件
    安装pg1、拉取镜像dockerpullpostgres2、创建数据卷dockervolumecreatepostgre-data3、创建并运行容器dockerrun-id--name=postgresql-vpostgre-data:/var/lib/postgresql/data-p5432:5432-ePOSTGRES_PASSWORD=123456-eLANG=C.UTF-8postgres上述设定了数......
  • 页面无法右键及复制的两种方式(js 与 css)
    1、全局在body上设置属性:<bodyonselectstart="returnfalse"onpaste="returnfalse"oncopy="returnfalse"oncut="returnfalse">2、js控制页面无法复制:<scripttype="text/javascript">//Methodone//&l......
  • 2024年AI预测报告-金融
    AndreessenHorowitz公司的合伙人MarcAndrusko表示银行和交易新工具到2024年,我们将看到雄心勃勃的创始人着手解决金融机构所面临的最为棘手的问题。 尽管全球投资银行和交易服务市场年收入接近3500亿美元,但目前仍主要依赖于20世纪80年代建立的内部部署系统和企业软件。尽管......
  • 关于ArcEngine在多线程模式下的注意点
    仅以我的环境来描述的我问题和解决方案,超出该范围的暂时没有考虑。一、环境ArcEngine10.2语言:C#.net版本:4.6.1二、需求创建GDB数据库,并从json文件把数据写入GDB中,包含了图形数据,为了兼顾效率,我使用了多线程来生成GDB,但也做了控制,一个线程只会对一个GDB进行操作。三、问题:......
  • vue 滚动条滚动到列表的某个区域时,将(负责的、参与的、管理的)区域的title固定到头部
    1、html1<div:id="item.id"class="list-item"v-for="(item,index)inokrTableDate":key="index">2<pclass="bold":class="{'is-fixed':isFixedFlag1&&item.id===�......
  • Qt:获取WIFI列表
    示例:使用QT来获取Windows电脑WIFI列表中所有WIFI的名称,实际是执行CMD命令来完成(netshwlanshownetworks) //获取WIFI列表QProcessprocess;process.start("netshwlanshownetworks");process.waitForStarted();process.waitForFinished();QStringcmd_res_text=QSt......