C# 学习逆变和协变

协变和逆变主要作用是为了让泛型接口、委托和数组在类型转换时更加灵活,减少不必要的代码,

C#只允许在接口和委托上使用out和in修饰逆变和协变,并对其行为进行了约束,避免破坏其类型安全,所以协变只允许返回,逆变只允许输入,

我们知道子类可以隐式转为父类,因为子类继承了父类的所有特性,父类有的行为字类必然也有,所以整个过程都是类型安全的,可以直接转换,
但是我们无法直接将List<string>隐式转换为List<object>,因为它们没有继承关系,想要转换的话就就需要先创建一个List<object>然后再把List<string>中的数据一个一个的扔到List<object>中,然而我们都知道它们的泛型类型本质上是有继承关系的,理论上不应该这么别扭,总感觉可以直接转换的样子,这个时候就能用到协变了,

通过协变就能使泛型类型的输出方向使用其派生类型,

先看一下官方默认实现的协变,平常用的时候是不是感觉很方便,

            object[] arr = new string[4];
            IEnumerable<object> values = new string[4];
            //List<object> list = new List<string>();  //报错,因为没有实现协变

我们也可以自己简单实现一个具有协变特性的集合

        public void Test() {
            MyList<string> list = new MyList<string>(2);
            list.Add("aaa");
            list.Add("bbb");
            IMyList<object> objectList = list;  //不报错了,因为IMyList<out T>实现了协变 
            // 通过协变接口访问元素
            Console.WriteLine(objectList[0]);
            Console.WriteLine(objectList[1]);
        }
        /// <summary>
        /// 使用 out 关键字标记 T 为协变类型参数
        /// </summary>
        public interface IMyList<out T> {
            T this[int index] { get; }  // 只能作为输出(返回值)
            int Count { get; }
        }
        public class MyList<T> : IMyList<T> {
            readonly T[] _items;
            public T this[int index] => _items[index];
            public int Count { get; private set; }
            public MyList(int capacity) {
                _items = new T[capacity];
            }
            public void Add(T item) {
                _items[Count++] = item;
            }
        }

 

逆变正好是反过来的,它允许泛型类型的输入方向使用基类型,这种行为也是安全的,因为基类型可以接受派生类型的所有输入,

C#的Action委托实现了逆变,它可以将父类泛型委托隐式转换为子类泛型委托,然后传递参数的时候使用子类型,

            void Output(object obj) {
                Console.WriteLine(obj.ToString());
            }
            Action<string> stringAction = Output; //利用逆变实现类型转换 
            stringAction("测试啊123");

也可以自己实现一个逆变委托,

        /// <summary>
        /// 使用in修饰委托让其具有逆变特性
        /// </summary>
        delegate void Output<in T>(T val);
        public void Test() {
            Output<object> output = p => Console.WriteLine(p.ToString());
            Output<string> outputString = output; //可以直接将父类泛型委托转换为子类泛型委托
            outputString("测试啊123abc");
        }

 

posted @ 2025-06-30 17:28  WmW  阅读(19)  评论(0)    收藏  举报