zoukankan      html  css  js  c++  java
  • C#基础系列-协变与逆变

    一、前言

      泛型参数的协变和逆变是在.NET4.0版本及版本之后提出的,解决的问题是在泛型参数存在继承关系的对象要进行隐式转换(里氏替换原则)提供类型安全的转换,在.NET4.0版本之前的时候泛型参数进行类型的转换要通过类型强制转换。所以带来了协变和逆变,协变是子类->父类,逆变是父类->子类,通过站的角度不一样进行转化,但其本质都是子类到父类通过协变只能是返回参数(out),逆变只能是传入参数(in)进行限制。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    namespace 泛型
    {
        /// <summary>
        /// 父类
        /// </summary>
        public class Parent{}
    
        /// <summary>
        /// 子类
        /// </summary>
        public class Child : Parent{}
    
        /// <summary>
        /// 接口
        /// </summary>
        /// <typeparam name="T"></typeparam>
        public interface IFun<out T>
        {
            /// <summary>
            /// 传入参数
            /// </summary>
            /// <param name="param"></param>
            // void Method1(T param);
            /// <summary>
            /// 返回参数
            /// </summary>
            /// <returns></returns>
            T Method2();
        }
    
        public class Fun<T> : IFun<T>
        {
            /// <summary>
            /// 传入参数方法
            /// </summary>
            /// <param name="param"></param>
            public void Method1(T param){}
    
            /// <summary>
            /// 返回参数的方法
            /// </summary>
            /// <returns></returns>
            public T Method2()
            {
                throw new NotImplementedException();
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Child child = new Child();
                Parent parent = child; // 子类替换父类
    
                List<Child> childs = new List<Child>();
                List<Parent> parents = childs; //无法进行隐式转换
                List<Parent> parentss = childs.Select(x => (Parent)x).ToList(); // 进行类型的强制转换
    
                IFun<Child> childFun = new Fun<Child>();
                IFun<Parent> parentFun = childFun; // 不加out关键字无法进行类型安全,通过out关键字让编译器子类到父类的隐式转换
    
                IFun<Parent> parentFunn = new Fun<Parent>();
                IFun<Child> childFunn = parentFunn; // 通过in关键字传递参数类型或者参数类型派生类子类到父类的隐式转换
            }
        }
    }

    二、定义 

      1、协变(子类->父类) 返回参数,其中要实现右边子类new对象转换成左边父类定义的变量,则返回参数T本来是右边定义的是子类要返回成父类,所以是子类转换成父类,是类型安全的(定义的是子类返回父类)。

      2、逆变(父类->子类) 传入参数,其中要实现右边父类new对象转换成左边子类定义的变量,则传入参数T本来是右边定义的是父类要用子类替换掉,所以是子类转换成父类,是类型安全的(定义的是父类传入子类)。

    三、总结

      1、因为在代码中进行类型的安全转换比较复杂,微软通过关键字in和out来定义逆变和协变,所以在编译阶段让编译器识别标识,实现参数的类型安全转换。

      2、协变与逆变中协变只能是返回参数,逆变只能是输入参数,如果互换则会出现父类到子类的转换,是类型不安全,编译不通过;

      3、只有在泛型接口和泛型委托中参数可以协变和逆变,比如类库中的Action<in T>、Func<out TResult,in TSource>、IEnumerable<out T>、IEnumerator<out T>等。

      4、协变和逆变本质都是子类到父类的类型安全隐式转换,协变意思是变化方向相同子类到父类,而协变是父类到子类其变化方向相反。通过协变只能传入参数和逆变只能输入参数的规定,在站的角度不一样,最终实现类型的安全转换。

    ps:对象的引用仅存在一种隐式类型转换,即子类型的对象引用到父类型的对象引用的转换。

  • 相关阅读:
    Linux基础命令-cd
    grep和egrep正则表达式
    SpringMVC源码阅读-一个请求主要处理流程DispatcherServlet(四)
    SpringMVC源码阅读-dispatcher组件初始化过程(三)
    SpringMVC源码阅读-Servlet WebApplicationContext初始化(二)
    SpringMVC源码阅读-Root WebApplicationContext初始化(一)
    logback源码阅读-配置文件解析过程(六)
    logback源码阅读-Encoder(五)
    logback源码阅读-Appender(四)
    logback源码阅读-Logger日志生成(三)
  • 原文地址:https://www.cnblogs.com/tuqunfu/p/12536008.html
Copyright © 2011-2022 走看看