复习

泛型基础,泛型类,泛型接口

namespace _1泛型基础
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            F1(10);
            F2("张三");
            F3(DateTime.Now);
            F4(10);
            F4<string>("zs");
            Console.WriteLine(Sum(10,20));
            Console.WriteLine(Sum1(10, 20.2));

            //1 传递的字符串类型
            People<string> people = new People<string>();
            people.Langue = "字符串类型";


            //2 传递一个对象类型 传递一个China类型
            People<China> p1  = new People<China>();
            p1.Langue = new China();



            //3调用有参数的构造函数
            People<Japan> p2 = new People<Japan>(new Japan());
            Console.WriteLine(p2.Every.Langue);


            // 泛型接口的使用
            Dog<int> d1 = new Dog<int>();


        }
        public void F1(int a)
        {
            Console.WriteLine($"参数的类型{a.GetType().FullName},参数值为:{a}");
        }
        public void F2(string a)
        {
            Console.WriteLine($"参数的类型{a.GetType().FullName},参数值为:{a}");
        }
        // F1和F2只是参数类型不一样,功能都是一样,可以把参数写成object类型,不建议这样操作,主要会产生装箱和拆箱操作
        public void F3(object a)
        {
            Console.WriteLine($"参数的类型{a.GetType().FullName},参数值为:{a}");
        }
        //定义成泛型方法 在声明的时候不知道T的具体类型, 调用的时候传递具体类型
        public void F4<T>(T a )
        {
            Console.WriteLine($"参数的类型{a.GetType().FullName},参数值为:{a}");
        }
        //有返回值的泛型方法
        public T Sum<T>(T a,T b)
        {
            //dynamic 动态定义变量
            dynamic res = a;
            res += b;
            //return  default (T); // 返回T的默认值
            return res;
        }
        public K Sum1<T,K>(T a, K b)
        {
            //dynamic 动态定义变量
            dynamic res = a;
            res += b;
            //return  default (T); // 返回T的默认值
            return res;
        }
    }
    public class China
    {
        public string Langue { get; set; } = "汉语";
        public void Eat( string content)
        {
            Console.WriteLine(content);
        }
    }
    public class Japan
    {
        public string Langue { get; set; } = "日语";
        public void Eat(string content)
        {
            Console.WriteLine(content);
        }
    }
    // 泛型类 使用这个类进行定义的时候 传递具体的类型 类型可以是基础类型 也可以是对象类型
    public class People<T>
    {
        public T Langue { get; set; }
        public void Eat(T content) // T 只是一个未知类型, 不是泛型方法
        {
            Console.WriteLine(content);
        }
        //无参数的构造函数
        public People()
        {

        }
        public T Every { get; set; }
        public People(T e) //构造函数的参数也可以使用泛型
        {
            Every = e;
        }

    }
    //泛型接口
    public interface IAnimal<T> 
    {
        T Sum(T a, T b);

    }
    public class Dog<T> : IAnimal<T>
    {
        public T Sum(T a, T b)
        {
            throw new NotImplementedException(); //NotImplementedException 未实现的异常
        }
    }
}

复习

泛型委托

namespace _2泛型委托
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            //内置的委托
            Action<int> a1 = a => { Console.WriteLine("无返回值的委托方法"); };
            a1(10);

            Func<int, int> a2 = a => a * 10;
            MessageBox.Show(a2(10)+""); //100

            Predicate<int> a3 = a => a % 2 == 0;
            MessageBox.Show(a3(10) + ""); //true


            //F1(10); 报错
            F1("");
            F1(new int[] {});
            F2(new People());
            F2(new Student());
        }
        // where T : class 引用类型
        public void F1<T>(T a) where T : class
        {

        }

        //  基类约束时
        public void F2<T>(T a) where T : People
        {

        }
    }
    public class People
    {

    }
    public class Student: People
    {

    }

}

新知识C# 协变与逆变

一、前置基础:普通父子类转换(非泛型)

1. 父子类关系定义

Bird(父类)、Sparrow(子类,麻雀继承鸟类)

public class Bird { }
public class Sparrow : Bird { }

2. 普通类转换规则(必考)

向上转换(隐式转换、安全)

子类对象 → 父类变量,自动转换,不报错

Bird bird = new Bird();
Sparrow sp = new Sparrow();

sp = new Sparrow();
bird = sp; // 子类赋给父类:隐式转换,完全合法

向下转换(显式强制转换、不安全)

父类对象 → 子类变量,直接写报错,必须强制转换

// sp = bird; // 直接赋值报错
sp = (Sparrow)bird; // 强制转换语法通过(运行可能报错)

总结口诀

子类转父类:自动隐式转换

父类转子类:必须强制转换


二、为什么需要协变逆变?(核心报错原因)

1. 泛型集合不能直接父子转换

List<Bird> birds = new List<Bird>();
List<Sparrow> sparrows = new List<Sparrow>();

// 报错!!!
// birds = sparrows;

2. 报错根本原因(面试必问)

Bird 和 Sparrow 是父子关系

但 List<Bird> 和 List<Sparrow> 没有任何继承关系

List 是密封类,不支持泛型隐式转换,所以直接赋值编译报错。

3. 解决方案:使用支持协变的接口

IEnumerable<out T> 接口自带 out 协变 特性

IEnumerable<Bird> birds1 = new List<Sparrow>();

此时编译通过,这就是协变


三、协变(Covariance)完整详解

1. 关键字

在T的类型前添加out关键字

2. 转换方向(正向)

子类泛型 → 父类泛型

小范围 → 大范围(和普通类隐式转换方向一致)

3. 适用场景

只能用于:泛型接口、泛型委托

作用:只读、做返回值(out 输出数据)

4. 系统内置协变接口/委托

IEnumerable<out T>

IReadOnlyList<out T>

Func<in T, out TResult>(返回值支持协变)

5. 代码案例:Func 委托协变

// Func<输入参数,返回值>
Func<int, Sparrow> f1 = a => new Sparrow();

// 协变:子类返回值泛型 → 父类返回值泛型
Func<int, Bird> f2 = f1;

解析:

f1 返回 Sparrow(子类)

f2 返回 Bird(父类)

out 支持子类泛型赋值给父类泛型,协变成功。


四、逆变(Contravariance)完整详解

1. 关键字

逆变:在T的类型前添加in关键字
1 in 主要是方法的输入参数进行使用

2. 转换方向(反向)

父类泛型 → 子类泛型

大范围 → 小范围(和普通转换相反)

3. 适用场景

只能用于:泛型接口、泛型委托

作用:只做输入参数(in 接收数据)

4. 系统内置逆变接口/委托

Action<in T>

Predicate<in T>

IComparer<in T>

Func<in T,out TResult>(参数支持逆变)

5. 逆变代码案例1:Predicate

Predicate<Bird> f3 = a => true;
// 逆变:父类泛型 → 子类泛型
Predicate<Sparrow> f4 = f3;

6. 逆变代码案例2:Action

Action<object> f5 = a => { };
// object(父) → Bird(子) 逆变成立
Action<Bird> f6 = f5;

7. 逆变代码案例3:Func参数逆变

Func<Bird, int> f7 = a => 10;
// 参数位置:父类 → 子类 逆变成立
Func<Sparrow, int> f8 = f7;

五、双变共存:Func 同时具备协变+逆变

1. 原理

Func<in T, out TResult>

参数 T:in 逆变(父→子)

返回值 TResult:out 协变(子→父)

所以 Func 是 C# 中唯一一个同时支持协变与逆变的常用委托。


六、协变 & 逆变 终极对照表(必背)

特性

协变

逆变

关键字

out

in

转换方向

子类泛型 → 父类泛型

父类泛型 → 子类泛型

读写角色

输出、返回值、只读

输入、参数、只写

典型载体

IEnumerable、返回值

Action、Predicate、参数

转换趋势

向上兼容(同普通隐式转换)

向下兼容(反向特殊转换)


七、核心易错点总结(考试必考坑点)

1、List<T> 不支持协变逆变,因为是密封类,只有接口/委托支持

2、out协变:子→父in逆变:父→子,千万不要记反

3、协变只能用于返回值、只读数据

4、逆变只能用于参数、输入数据

5、Func 双变、Action只有逆变、IEnumerable只有协变

6、普通类有继承关系 ≠ 泛型集合有继承关系


八、极简背诵口诀

out协变,子转父,输出只读向上走

in逆变,父转子,输入参数反向守

集合List不支持,接口委托才拥有

Func双变最特殊,参逆变、返协变

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐