一、协变与逆变的概念
1、协变(共变)
协变是一种类型关系,通过它,我们可以在泛型委托或泛型接口中进行父类到子类的隐式转换。具体而言,如果类型 A 是类型 B 的子类型(A ≤ B),则支持协变的情况下,泛型类型参数 T 可以被隐式转换为 T。这使得我们可以在一些上下文中更通用地使用较为抽象的类型。
// 协变示例
public interface ICoVariant<out T>
{
T GetItem();
}
public class BaseClass { }
public class DerivedClass : BaseClass { }
// 使用协变的接口
ICoVariant<BaseClass> baseInstance = new CoVariant<DerivedClass>();
BaseClass item = baseInstance.GetItem(); // 隐式转换,获取 DerivedClass 实例
2、逆变(反变)
逆变则允许在泛型委托或泛型接口中进行子类到父类的隐式转换。如果类型 A 是类型 B 的子类型(A ≤ B),则支持逆变的情况下,泛型类型参数 T 可以被隐式转换为 T。逆变为我们提供了更多的灵活性,允许我们传递更具体类型的对象给接受更抽象类型的泛型接口或委托。
// 逆变示例
public interface IContraVariant<in T>
{
void PrintItem(T item);
}
public class BaseClass { }
public class DerivedClass : BaseClass { }
// 使用逆变的接口
IContraVariant<DerivedClass> derivedInstance = new ContraVariant<BaseClass>();
derivedInstance.PrintItem(new DerivedClass()); // 隐式转换,接受 BaseClass 实例
3、继承关系与类型转换
继承关系与类型转换是协变和逆变的基础。在协变的情况下,随着类型的派生程度增加,泛型类型参数的继承关系也得到了保持。而在逆变的情况下,派生程度更大的类型可以被隐式地转换为派生程度更小的类型。这些关系可以用公式表示如下:
- 当 A ≤ B 时,若 f(x) 是逆变的,则 f(B) ≤ f(A) 成立。
- 当 A ≤ B 时,若 f(x) 是协变的,则 f(A) ≤ f(B) 成立。
这些公式强调了在协变和逆变中,类型转换的方向是根据类型的继承关系而变化的。
二、逆变与协变的运用
1、泛型委托
a、协变
协变允许我们在泛型委托中进行父类到子类的隐式转换。请参考下面的示例:
public delegate T DelegateFuncA<out T>(); //支持协变
DelegateFuncA<object> funcObject = null;
DelegateFuncA<string> funcString = null;
funcObject = funcString; //协变
在这个例子中,DelegateFuncA 声明了一个支持协变的泛型委托。通过将 funcString 赋值给 funcObject,我们实现了从 string 到 object 的协变转换。这种灵活性可以在一些场景中提高代码的可读性和可维护性。
b、逆变
逆变允许我们在泛型委托中进行子类到父类的隐式转换。请参考下面的示例:
public delegate void DelegateFuncB<in T>(T param); //支持逆变
DelegateFuncB<object> actionObject = null;
DelegateFuncB<string> actionString = null;
actionString = actionObject; //逆变
在这个示例中,DelegateFuncB 是一个支持逆变的泛型委托。通过将 actionObject 赋值给 actionString,我们实现了从 object 到 string 的逆变转换。逆变为我们提供了更多的灵活性,使得我们能够更容易地传递具有更具体类型的方法给接受更抽象类型的委托。
2、泛型接口
a、协变
协变在泛型接口中的应用同样是非常有意义的。请参考下面的示例:
public delegate T InterfaceFuncA<out T>(); //支持协变
InterfaceFuncA<object> interfaceFuncObject = null;
InterfaceFuncA<string> interfaceFuncString = null;
interfaceFuncObject = interfaceFuncString; //协变
在这个例子中,我们使用支持协变的泛型接口 InterfaceFuncA。通过将 interfaceFuncString 赋值给 interfaceFuncObject,我们实现了从 string 到 object 的协变转换。
b、逆变
逆变同样可以在泛型接口中发挥作用。请参考下面的示例:
public delegate void InterfaceFuncB<in T>(T param); //支持逆变
InterfaceFuncB<object> interfaceFuncObjectB = null;
InterfaceFuncB<string> interfaceFuncStringB = null;
interfaceFuncStringB = interfaceFuncObjectB; //逆变
在这个例子中,我们使用支持逆变的泛型接口 InterfaceFuncB。通过将 interfaceFuncObjectB 赋值给 interfaceFuncStringB,我们实现了从 object 到 string 的逆变转换。
3、数组逆变
逆变同样可以在数组中发挥作用,使得派生程度更大的类型的数组能够隐式转换为派生程度更小的类型的数组。请参考下面的示例: