本章的主题是可变性(variance),这里的可变性更多的是指基类和派生类之间的转换。可变性分为三种:协变(covariance)、逆变(contravariance)和不变(invariance)。
文章目录
- 1. 协变
- 1.1 协变的概念
- 1.2 语法
- 1.3 使用场景
- 1.4 代码例子:使用委托实现协变
- 1.5 代码例子:LINQ 中使用协变
- 2. 逆变
- 2.1 逆变的概念
- 2.2 语法
- 2.3 使用场景
- 2.4 代码示例:委托中使用逆变
- 2.5 代码示例:事件处理中使用逆变
- 3. 一些问题
- 3.1 协变和多态的区别
- 概念区别
- 主要区别总结
1. 协变
在C#中,协变(Covariance)是一种允许将派生类类型替换为基类类型的特性。这种特性通常用于泛型类型或委托中,特别是在返回类型时。协变的主要目的是提高代码的灵活性和可重用性,使得在使用派生类时能够有效地利用基类的接口或方法。
1.1 协变的概念
协变是指在泛型类型的使用中,允许将某个类型参数替换为该参数的派生类。换句话说,协变允许你在泛型委托或接口中使用更具体的类型。在C#中,协变通常用于返回值的情况。
1.2 语法
在C#中,可以通过使用out关键字来声明协变类型参数。下面是协变的基本语法:
public delegate TResult MyDelegate<out TResult>();
这里,TResult 参数前面加了out关键字,表明这个类型参数是协变的。返回类型可以是派生类。
1.3 使用场景
协变的常见使用场景包括:
- 委托:在使用委托时,协变允许将一个返回派生类的委托赋值给返回基类的委托。
- LINQ:在LINQ查询中,使用协变来处理不同类型的集合。
- 事件处理:在事件处理程序中,使用协变来处理不同类型的事件。
1.4 代码例子:使用委托实现协变
using System;// 基类
public class Animal
{public virtual void Speak(){Console.WriteLine("Animal speaks");}
}// 派生类
public class Dog : Animal
{public override void Speak(){Console.WriteLine("Dog barks");}
}// 定义一个协变的委托
public delegate T AnimalDelegate<out T>();class Program
{static void Main(){// 将返回Dog类型的委托赋值给返回Animal类型的委托AnimalDelegate<Animal> animalDelegate = GetDog;// 调用委托并输出结果Animal animal = animalDelegate();animal.Speak(); // 输出: Dog barks}static Dog GetDog(){return new Dog();}
}
输出:
Dog barks
1.5 代码例子:LINQ 中使用协变
using System;
using System.Collections.Generic;
using System.Linq;public class Animal
{public string Name { get; set; }
}public class Dog : Animal { }class Program
{static void Main(){List<Dog> dogs = new List<Dog>{new Dog { Name = "Buddy" },new Dog { Name = "Max" }};// 使用LINQ进行查询,并返回基类类型的集合IEnumerable<Animal> animals = dogs.Select(d => d);foreach (var animal in animals){Console.WriteLine(animal.Name); // 输出: Buddy, Max}}
}
使用协变时,应该注意以下几点:
- 只能在返回值中使用协变,而不能在方法参数中使用。
- 协变使得代码更加灵活,但也可能引入类型安全问题,因此在使用时应谨慎。
2. 逆变
在C#中,逆变(Contravariance)是与协变相反的特性,允许将基类类型替换为派生类类型。逆变主要用于参数类型的上下文,特别是在方法参数时。通过逆变,我们可以使用更通用的类型来替代特定的类型,从而提高代码的灵活性和可重用性。
2.1 逆变的概念
逆变是指在泛型类型的使用中,允许将某个类型参数替换为该参数的基类。这种特性通常在需要处理不同类型的对象时非常有用。表现在代码上就是某个函数的参数类型是基类类型,但是可以接受其派生类类型的实参。
2.2 语法
在C#中,可以通过使用 in 关键字来声明逆变类型参数。下面是逆变的基本语法:
public delegate void MyDelegate<in T>();
2.3 使用场景
逆变的常见使用场景包括:
- 委托:在使用委托时,逆变允许将一个接受派生类的委托赋值给接受基类的委托。
- 事件处理:在事件处理程序中,使用逆变来处理不同类型的事件。
- 集合操作:在处理集合时,逆变可以帮助简化参数类型的定义。
2.4 代码示例:委托中使用逆变
下面的程序实现了一个基类类型的委托指向参数类型为基类类型的方法,但是在执行委托时,传入给委托的参数类型为派生类类型。
using System;// 基类
public class Animal
{public string Name { get; set; }
}// 派生类
public class Dog : Animal { }// 定义一个逆变的委托
public delegate void AnimalAction<in T>(T animal);class Program
{static void Main(){// 将接受Animal类型的委托赋值给接受Dog类型的委托AnimalAction<Animal> animalAction = MakeSound;Dog dog = new Dog { Name = "Buddy" };animalAction(dog); // 输出: Buddy makes a sound}static void MakeSound(Animal animal){Console.WriteLine($"{animal.Name} makes a sound");}
}
2.5 代码示例:事件处理中使用逆变
下面的代码中实现了使用事件和逆变,统一处理不同用户的目的。
public void AddUser(User user)
输出:
John has been added.
Admin has been added.
使用 in 和 out 关键字只适用于委托和接口,不适用于类、结构和方法。
不包括 in 和 out 关键字的委托和接口类型参数叫做不变。这些类型参数不能用于协变和逆变。
3. 一些问题
3.1 协变和多态的区别
概念区别
- 多态:多态是指子类可以替代父类的实例,调用相同的方法但可能会有不同的实现。在面向对象编程中,常通过继承和接口来实现多态性。
public class Animal
{public virtual void Speak(){Console.WriteLine("Animal speaks");}
}public class Dog : Animal
{public override void Speak(){Console.WriteLine("Woof!");}
}Animal myDog = new Dog();
myDog.Speak(); // 输出: Woof!
- 协变:协变是指在泛型类型参数中允许用派生类替代基类。在 C# 中,协变通常与泛型委托和接口相关,允许使用更具体的类型作为返回值。
public delegate T CovariantDelegate<out T>();public class Animal { }
public class Dog : Animal { }public static Dog GetDog() => new Dog();CovariantDelegate<Animal> animalDelegate = GetDog;
Animal animal = animalDelegate(); // 使用协变
主要区别总结
概念范围:
- 多态是一个更广泛的概念,涵盖了通过接口和继承实现不同类型之间的行为相同。
- 协变是关于泛型类型参数的特定实现,主要用于返回值的场景。
实现方式:
- 多态通常通过方法重写(override)和接口实现来实现,允许子类定义父类方法的具体实现。
- 协变是通过在泛型定义中使用 out 关键字来实现,允许使用更具体的类型作为返回值。
适用场景:
- 多态主要用于运行时行为的动态选择,允许对象通过父类接口调用不同的实现。
- 协变主要用于数据结构和类型安全的情况下,特别是在返回类型的灵活性方面。
各位道友,码字不易,记得一键三连呐。