C#中的协变性和你便性
偶然看到迭代器里面的接口
public interface IEnumerable<out T> : IEnumerable
{
//
// 摘要:
// Returns an enumerator that iterates through the collection.
//
// 返回结果:
// An enumerator that can be used to iterate through the collection.
IEnumerator<T> GetEnumerator();
}
自己继承自己,泛型还使用了out;查阅因此了解到了协变性和逆变性这两个概念
在 C# 泛型中,out 是一种 协变性 修饰符,用于泛型接口或委托,表示类型参数可以是指定类型或其派生类型。协变性允许在某些场景中使用更加具体的类型,而不是仅限于声明的类型。
out 的作用
-
协变性 (Covariance):
- 允许将泛型参数类型替换为 更具体 的派生类型。
- 只能出现在接口或委托中,用于 输出位置 的类型参数。
- 在实际实现中,
out类型参数 只能作为返回值,不能用作输入参数。
-
约束
- 类型参数标记为
out后,只能用于返回类型(输出位置),不能用于方法参数(输入位置)。 - 这是因为协变性保证派生类实例仍然可以用作基类实例,但输入时可能导致类型安全问题。
- 类型参数标记为
在 IEnumerable<out T> 中的作用
对于 IEnumerable<out T>,它的定义:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
协变性应用
out T允许IEnumerable<T>的派生类在需要IEnumerable<Base>的地方使用。- 场景:
- 假设有一个基类
Animal和派生类Dog:class Animal { } class Dog : Animal { } - 使用
IEnumerable<Animal>时,可以将IEnumerable<Dog>传递给方法或变量:IEnumerable<Dog> dogs = new List<Dog>(); IEnumerable<Animal> animals = dogs; // 合法,因为 T 被标记为 out
- 假设有一个基类
为什么可以这样做?
- 协变性确保返回值(如
IEnumerator<T>)的类型安全:- 迭代
dogs时返回的是Dog类型,它是Animal类型的子类,完全符合类型安全要求。
- 迭代
协变性的优点
- 灵活性:
- 允许在泛型类型之间进行更多的类型转换,而不需要额外的类型转换代码。
- 代码复用:
- 同一方法可以操作基类或派生类的泛型集合,而不需要专门为每种类型实现方法。
out 和 in 的对比
| 修饰符 | 作用 | 用途 | 例子 |
| ------ | -------------------------- | -------------------------------- | -------------------- |
| out | 协变性(允许更具体的类型) | 只能出现在输出位置(如返回值) | IEnumerable<out T> |
| in | 逆变性(允许更泛化的类型) | 只能出现在输入位置(如参数类型) | IComparer<in T> |
例子
// 基类和派生类
class Animal { public string Name { get; set; } }
class Dog : Animal { public string Breed { get; set; } }
class Program
{
static void Main()
{
// 泛型集合
IEnumerable<Dog> dogs = new List<Dog>
{
new Dog { Name = "Buddy", Breed = "Labrador" },
new Dog { Name = "Charlie", Breed = "Beagle" }
};
// 协变性:将 IEnumerable<Dog> 转为 IEnumerable<Animal>
IEnumerable<Animal> animals = dogs;
foreach (var animal in animals)
{
Console.WriteLine(animal.Name); // 输出狗的名字
}
}
}
总结
out修饰符 是用于泛型接口和委托的协变性实现。- 它使得泛型接口更灵活,支持派生类到基类的类型转换。
IEnumerable<out T>是协变性的经典例子,支持多态的迭代场景。- 协变性使代码更简洁,同时保持类型安全性。
在 C# 泛型中,in 是一种 逆变性 修饰符,允许将泛型类型参数替换为 更泛化 的基类类型。逆变性和协变性相反,它主要适用于泛型接口或委托中的 输入位置 类型参数。
逆变性 (Contravariance) 的作用
-
更泛化的类型:
- 逆变性允许将泛型类型参数替换为它的 基类 类型。
- 只能用于输入位置的类型参数。
-
场景限制:
- 类型参数标记为
in后,只能作为方法参数类型(输入位置)。 - 不能用作返回值(输出位置),因为逆变性意味着允许更泛化的类型,返回值可能会导致类型安全问题。
- 类型参数标记为
在 IComparer<in T> 中的作用
IComparer<in T> 的定义如下:
public interface IComparer<in T>
{
int Compare(T x, T y);
}
逆变性应用
- 假设有一个基类
Animal和派生类Dog:class Animal { public string Name { get; set; } } class Dog : Animal { public string Breed { get; set; } } IComparer<Animal>可以被用来比较Dog对象,因为Dog是Animal的派生类。IComparer<Animal> animalComparer = new AnimalComparer(); IComparer<Dog> dogComparer = animalComparer; // 合法,因为 T 被标记为 in
为什么可以这样做?
- 逆变性保证了传入方法的类型参数是基类或派生类的实例,输入类型仍然满足类型安全要求。
逆变性的优点
- 通用性:
- 允许在泛型接口中接受更广泛的类型输入,而不需要额外的类型转换。
- 代码复用:
- 编写针对基类的泛型代码,可以直接应用于派生类,避免重复代码。
in 和 out 的对比
| 修饰符 | 作用 | 用途 | 例子 |
| ------ | ------------------------ | ---------------------- | -------------------- |
| out | 协变性:允许更具体的类型 | 输出位置(如返回值) | IEnumerable<out T> |
| in | 逆变性:允许更泛化的类型 | 输入位置(如参数类型) | IComparer<in T> |
例子
以下代码展示了 IComparer<in T> 中的逆变性:
基类和派生类
class Animal { public string Name { get; set; } }
class Dog : Animal { public string Breed { get; set; } }
泛型比较器
class AnimalComparer : IComparer<Animal>
{
public int Compare(Animal x, Animal y)
{
return string.Compare(x.Name, y.Name);
}
}
逆变性应用
class Program
{
static void Main()
{
// 基类比较器
IComparer<Animal> animalComparer = new AnimalComparer();
// 派生类比较器(逆变性允许)
IComparer<Dog> dogComparer = animalComparer;
// 比较两只狗
Dog dog1 = new Dog { Name = "Buddy", Breed = "Labrador" };
Dog dog2 = new Dog { Name = "Charlie", Breed = "Beagle" };
int result = dogComparer.Compare(dog1, dog2);
Console.WriteLine(result > 0 ? "dog1 comes after dog2" : "dog1 comes before dog2");
}
}
总结
in修饰符 实现了泛型接口和委托的逆变性。- 它允许将派生类类型参数替换为基类类型,从而提升代码通用性。
- 典型用法:
IComparer<in T>和IEqualityComparer<in T>等输入为类型参数的泛型接口。
- 逆变性保证了泛型接口在输入操作上的类型安全,同时让代码更灵活。