基本概念 协变:能够使用比原始指定的派生类型的派生程度更大(更具体)的类型。例如 IFoo<父类> = IFoo<子类> 逆变:能够使用比原始指定的派生类型的派生程度更新(更抽象)的类型。例如 IBar<子类> = IBar<父类> 关键字out和in 协变和逆变在泛型参数中的表现方式,out关键字表示协变,in关键字表示逆变。二者只能在泛型接口或者委托中使用。 理解协变和逆变 看完上面的定义是不是一脸懵逼~~~。看不懂就对了,且定义语句的歧义性很大。让我们大脑赶紧清空下!!首先记住一点明确的概念,类的多态展示一定是通过基类来表示,派生的具体类都是可转化为基类,而不能走相反的流程。 下面我们 在C#编程语言中,协变(Covariance)和逆变(Contravariance)是泛型的关键特性,它们允许在特定情况下放宽类型约束,从而提高代码的灵活性和重用性。协变允许将派生类型替换为基类型,而逆变则是允许将基类型替换为派生类型。这两个概念主要应用于泛型接口和委托。 让我们来看看协变。在C#中,协变允许将一个泛型接口或委托实例赋值给另一个具有相同类型但参数类型更具体的实例。比如,如果有一个接口`IFoo<T>`,协变意味着`IFoo<父类>`可以赋值给`IFoo<子类>`,因为子类是父类的派生类型。C#通过在泛型接口定义中的`out`关键字来表示协变。例如,`IEnumerable<out T>`接口就是协变的,因此可以将`IEnumerable<Dog>`赋值给`IEnumerable<Animal>`。 接下来是逆变,它与协变相反。逆变允许将基类型替换为派生类型作为方法的参数。例如,如果有一个委托`Action<in T>`,逆变意味着`Action<子类>`可以赋值给`Action<父类>`,因为这里的`in`关键字表示T参数是逆变的。逆变通常适用于方法的输入参数,因为它允许接收更广泛的类型作为参数。 以下是一些代码示例来帮助理解协变和逆变: ```csharp // 基类和派生类 public class Animal { public void Eat() { } } public class Dog : Animal { public void Run() { } } // 示例1:协变 // IEnumerable<Animal> 是协变的,所以可以赋值 IEnumerable<Animal> animals = new List<Animal>(); IEnumerable<Animal> animalsFromDogs = new List<Dog>(); // 示例2:逆变 // Action<Animal> 是逆变的,可以接受Animal类型的参数 Action<Animal> actionAnimal = a => a.Eat(); Action<Animal> actionFromDog = d => d.Run(); // 示例3:错误示例 - 不能将派生类赋值给基类实例(不支持协变) List<Animal> animals = new List<Dog>(); // 错误 // 示例4:错误示例 - 不能将逆变的Action<Animal>赋值给Action<Dog>(不支持逆变) Action<Dog> actionDog = actionAnimal; // 错误 ``` 理解协变和逆变的关键在于多态的概念。在面向对象编程中,多态通常通过基类引用派生类对象实现。协变和逆变扩展了这个概念,使得在某些泛型上下文中,派生类和基类可以更灵活地互换。 在设计泛型接口或委托时,选择使用`in`或`out`关键字取决于接口或委托的用途。如果接口方法返回类型需要是协变的,如`IEnumerable<out T>`,则应使用`out`。如果委托的方法参数需要是逆变的,如`Action<in T>`,则使用`in`。 协变和逆变是C#泛型中提高类型兼容性和代码重用性的关键特性。正确理解和使用它们可以使代码更加灵活,同时保持类型安全。不要过于关注官方定义,而是要记住`out`代表协变,`in`代表逆变,并根据实际场景选择使用。
- 粉丝: 10
- 资源: 916
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助