C#泛型的逆变协变之个人理解

1. 什么是泛型

泛型是指在定义类、接口和方法时使用类型参数,以便在使用这些类、接口和方法时可以指定具体的类型。通过泛型,我们可以编写更具有通用性和灵活性的代码。

例如:定义一个泛型类List<T>,表示一个可以存储任意类型的列表。

2. 逆变和协变

2.1 逆变(Contravariance)

逆变是指可以使用派生类型代替基类类型的关系,也就是可以将一个较专用的类型赋值给一个较通用的类型。

例如:有一个基类Animal和一个派生类Cat,那么我们可以将一个IEnumerable<Animal>实例赋值给一个IEnumerable<Cat>变量。

2.2 协变(Covariance)

协变是指可以使用基类类型代替派生类型的关系,也就是可以将一个较通用的类型赋值给一个较专用的类型。

例如:有一个派生类Cat和一个基类Animal,那么我们可以将一个IEnumerable<Cat>实例赋值给一个IEnumerable<Animal>变量。

3. 逆变和协变的使用

3.1 泛型接口中的逆变和协变

在泛型接口中,我们可以使用in关键字来标记逆变的类型参数,使用out关键字来标记协变的类型参数。

例如:

public interface IComparer<in T> 

{

int Compare(T x, T y);

}

public interface IEnumerable<out T>

{

IEnumerator<T> GetEnumerator();

}

上述代码中,IComparer<in T>接口的T类型参数标记为逆变,表示可以接受T或T的基类类型;IEnumerable<out T>接口的T类型参数标记为协变,表示可以返回T或T的派生类类型的实例。

3.2 泛型委托中的逆变和协变

在泛型委托中,逆变和协变的使用与泛型接口类似。

例如:

public delegate void Action<in T>(T obj);

public delegate TResult Func<in T, out TResult>(T arg);

上述代码中,Action<in T>委托的T类型参数标记为逆变,表示可以接受T或T的基类类型;Func<in T, out TResult>委托的T类型参数标记为逆变,表示可以接受T或T的基类类型,同时TResult类型参数标记为协变,表示可以返回TResult或TResult的派生类类型的结果。

4. 逆变和协变的作用

逆变和协变的使用可以使代码更加灵活和通用,提供更好的代码复用性和可扩展性。

4.1 逆变的作用

逆变可以让我们在使用具体类型时,可以接受其基类类型,这在一些接受参数的方法中非常有用。

例如:有一个方法PrintAnimal,接受Animal类型的参数,我们可以传入Cat类型的实例,因为Cat是Animal的派生类。

public void PrintAnimal(Animal animal)

{

Console.WriteLine(animal.Name);

}

Animal cat = new Cat("Tom");

PrintAnimal(cat); // 输出 "Tom"

4.2 协变的作用

协变可以让我们在返回结果时,可以返回其派生类类型,这在一些返回值类型的方法中非常有用。

例如:有一个方法GetAnimals,返回IEnumerable<Animal>类型的结果,我们可以返回IEnumerable<Cat>类型的实例,因为Cat是Animal的派生类。

public IEnumerable<Animal> GetAnimals()

{

yield return new Cat("Tom");

yield return new Dog("Jerry");

}

IEnumerable<Cat> cats = GetAnimals().Where(a => a is Cat).Cast<Cat>();

上述代码中,GetAnimals方法返回的是IEnumerable<Animal>类型的结果,我们通过使用协变,可以将其赋值给一个IEnumerable<Cat>类型的变量,并且可以进一步筛选出只包含Cat类型的实例。

5. 总结

通过逆变和协变的使用,我们可以在泛型类、接口和委托中更灵活地处理类型的转换,提供更好的代码复用性和可扩展性。

在实际开发中,我们应根据需要合理使用泛型的逆变和协变,以提高代码的可读性和可维护性。

后端开发标签