在C#中,委托的协变(covariance)和逆变(contravariance)是通过泛型委托的类型参数上的 in
和 out
修饰符来实现的。这些修饰符指定了委托类型参数是只能作为输入(逆变)还是只能作为输出(协变)使用。
协变(Covariance)
协变允许将一个派生类的委托实例赋值给一个基类委托类型的变量。这意味着,如果有一个返回派生类实例的方法,可以将其赋值给一个返回基类实例的委托类型。在委托类型参数上使用 out
关键字来标记协变。
例如:
public delegate TOut CovariantDelegate<out TOut>();public class BaseClass { }
public class DerivedClass : BaseClass { }public class Program
{public static DerivedClass CreateDerived(){return new DerivedClass();}static void Main(){CovariantDelegate<DerivedClass> derivedDelegate = CreateDerived;CovariantDelegate<BaseClass> baseDelegate = derivedDelegate; // 协变BaseClass instance = baseDelegate();}
}
在这个例子中,CovariantDelegate<DerivedClass>
可以被隐式转换为 CovariantDelegate<BaseClass>
,因为 DerivedClass
是 BaseClass
的派生类。
逆变(Contravariance)
逆变允许将一个基类的方法(或lambda表达式)赋值给一个派生类委托类型的变量。这意味着,如果有一个接受基类实例作为参数的方法,可以将其赋值给一个接受派生类实例的委托类型。在委托类型参数上使用 in
关键字来标记逆变。
例如:
public delegate void ContravariantDelegate<in TIn>(TIn arg);public class BaseClass { }
public class DerivedClass : BaseClass { }public class Program
{public static void ProcessBaseClass(BaseClass obj){// 处理BaseClass类型的对象}static void Main(){ContravariantDelegate<BaseClass> baseDelegate = ProcessBaseClass;ContravariantDelegate<DerivedClass> derivedDelegate = baseDelegate; // 逆变DerivedClass instance = new DerivedClass();derivedDelegate(instance);}
}
在这个例子中,ContravariantDelegate<BaseClass>
可以被隐式转换为 ContravariantDelegate<DerivedClass>
,因为委托接受的是 DerivedClass
类型的参数,而 DerivedClass
是 BaseClass
的派生类,所以它可以被安全地传递给接受 BaseClass
类型参数的方法。
底层实现
在底层,C# 编译器和CLR(公共语言运行时)共同处理协变和逆变。编译器负责在编译时检查类型安全性,确保协变和逆变的使用是有效的。CLR则负责在运行时支持这些特性,通过类型检查和类型转换来确保委托的调用是安全的。
- 对于协变,CLR在调用委托时,会执行一个称为“协变返回”的类型转换,将实际的返回类型转换为委托声明的返回类型。
- 对于逆变,CLR在调用委托时,会执行一个称为“逆变参数”的类型检查,确保传递给委托的参数是委托参数类型的派生类(或兼容类型)。
这些机制允许C#在保持类型安全的同时,提供灵活的委托使用方式。