跳到主要内容

C# 类型参数协变与逆变

在C#中,泛型类型参数的协变(Covariance)和逆变(Contravariance)是高级特性,它们允许我们在使用泛型类型时更灵活地处理类型之间的关系。理解这些概念可以帮助你编写更通用、更安全的代码。

什么是协变与逆变?

协变和逆变是描述类型参数在继承关系中的行为方式的概念:

  • 协变(Covariance):允许使用比原始类型更具体的类型。例如,如果 IEnumerable<Derived> 可以赋值给 IEnumerable<Base>,那么 IEnumerable<T> 就是协变的。
  • 逆变(Contravariance):允许使用比原始类型更通用的类型。例如,如果 Action<Base> 可以赋值给 Action<Derived>,那么 Action<T> 就是逆变的。

协变与逆变的实际应用

协变示例

协变通常用于返回类型。例如,IEnumerable<T> 接口是协变的,这意味着你可以将一个 IEnumerable<Derived> 赋值给 IEnumerable<Base>

csharp
class Base { }
class Derived : Base { }

IEnumerable<Derived> derivedList = new List<Derived>();
IEnumerable<Base> baseList = derivedList; // 协变

在这个例子中,IEnumerable<Derived> 可以赋值给 IEnumerable<Base>,因为 DerivedBase 的子类。

逆变示例

逆变通常用于输入参数。例如,Action<T> 委托是逆变的,这意味着你可以将一个 Action<Base> 赋值给 Action<Derived>

csharp
Action<Base> baseAction = (baseObj) => Console.WriteLine(baseObj.GetType().Name);
Action<Derived> derivedAction = baseAction; // 逆变

derivedAction(new Derived()); // 输出: Derived

在这个例子中,Action<Base> 可以赋值给 Action<Derived>,因为 BaseDerived 的父类。

实际案例

协变的应用场景

假设你有一个处理动物列表的应用程序:

csharp
class Animal { }
class Dog : Animal { }

void ProcessAnimals(IEnumerable<Animal> animals)
{
foreach (var animal in animals)
{
Console.WriteLine(animal.GetType().Name);
}
}

IEnumerable<Dog> dogs = new List<Dog> { new Dog(), new Dog() };
ProcessAnimals(dogs); // 协变允许传递 IEnumerable<Dog> 给 IEnumerable<Animal>

在这个例子中,ProcessAnimals 方法可以接受 IEnumerable<Dog>,因为 IEnumerable<T> 是协变的。

逆变的应用场景

假设你有一个处理事件的系统:

csharp
class Event { }
class MouseEvent : Event { }

void HandleEvent(Action<Event> eventHandler)
{
eventHandler(new Event());
}

Action<MouseEvent> mouseEventHandler = (mouseEvent) => Console.WriteLine("Mouse event handled");
HandleEvent(mouseEventHandler); // 逆变允许传递 Action<MouseEvent> 给 Action<Event>

在这个例子中,HandleEvent 方法可以接受 Action<MouseEvent>,因为 Action<T> 是逆变的。

总结

协变和逆变是C#中强大的泛型特性,它们允许我们在类型层次结构中更灵活地使用泛型类型。协变主要用于返回类型,而逆变主要用于输入参数。理解这些概念可以帮助你编写更通用、更安全的代码。

附加资源

练习

  1. 创建一个协变的接口 IEnumerable<T> 并实现它。
  2. 创建一个逆变的委托 Action<T> 并使用它处理不同类型的事件。
  3. 尝试在实际项目中应用协变和逆变,观察它们如何提高代码的灵活性。

通过练习,你将更好地掌握协变与逆变的实际应用,并能够在自己的项目中灵活运用这些高级特性。