天天看點

你了解C#的協變和逆變嗎

你了解C#的協變和逆變嗎

從C# 4.0開始,泛型接口和泛型委托都支援協變和逆變,由于曆史原因,數組也支援協變。

裡氏替換原則:任何基類可以出現的地方,子類一定可以出現。

協變(out)

協變:即自然的變化,遵循裡氏替換原則,表現在代碼上則是任何基類都可以被其子類指派,如Animal = Dog、Animal = Cat

使用out關鍵字聲明(注意和方法中修飾參數的out含義不同)

被标記的參數類型隻能作為方法的傳回值(包括隻讀屬性)

在沒有協變時:

abstract class Animal {}

class Dog : Animal {}

class Cat : Animal {}

interface IPoppable

{

T Pop();           

}

class MyStack : IPoppable

private int _pos;
private readonly T[] _data = new T[100];

public void Push(T obj) => _data[_pos++] = obj;
public T Pop() => _data[--_pos];           

以下代碼是無法通過編譯的

var dogs = new MyStack();

IPoppable animals1 = dogs; // 此處會發生編譯錯誤

Stack animals2 = dogs; // 此處會發生編譯錯誤

此時,我們如果需要為動物園飼養員新增一個輸入參數為Stack飼喂的方法,一個比較好的方法是新增一個限制泛型方法:

class Zookeeper

public static void Feed<T>(IPoppable<T> animals) where T : Animal {}           

// 或者

public static void Feed<T>(Stack<T> animals) where T : Animal {}           

// Main

Zookeeper.Feed(dogs);

現在,C#增加了協變

使IPoppable接口支援協變

// 僅僅增加了一個 out 聲明

T Pop();           

簡化Feed方法

public static void Feed(IPoppable<Animal> animals) {}           

協變的天然特性——僅可作為方法傳回值,接口(或委托)外部無法進行元素添加,確定了泛型類型安全性,是以不用擔心Dog的集合中出現Cat

常用的支援協變的接口和委托有:

IEnumerable

IEnumerator

IQueryable

IGrouping

Func等共17個

Converter

IEnumerable dogs = Enumerable.Empty();

IEnumerable animals = dogs;

var dogList = new List();

IEnumerable animals = dogList;

另外,由于曆史原因,數組也支援協變,例如

var dogs = new Dog[10];

Animal[] animals = dogs;

但是無法保證類型安全性,以下代碼可正常進行編譯,但是運作時會報錯

animals[0] = new Cat(); // 運作時會報錯

逆變(in)

逆變:即協變的逆向變化,實質上還是遵循裡氏替換的原則,将子類指派到基類上

使用in關鍵字聲明

被标記的參數類型隻能作為方法輸入參數(包括隻寫屬性)

例如:

interface IPushable

void Push(T obj);           

class MyStack : IPushable

private int _pos;
private readonly T[] _data = new T[100];

public void Push(T obj) => _data[_pos++] = obj;
public T Pop() => _data[--_pos];           

var animals = new MyStack();

animals.Push(new Cat());

IPushable dogs = animals;

dogs.Push(new Dog());

逆變的天然特性——僅可作為方法輸入參數,接口(或委托)無法進行元素擷取,即隻能将子類指派到父類上,進而保證了類型安全性。

另外,常用支援逆變的接口和委托有:

IComparer

IComparable

IEqualityComparer

Action等共16個

Predicate

Comparison

Action animalAction = new Action(a => { });

Action DogAction = animalAction;

作者:xiaoxiaotank

出處:

https://www.cnblogs.com/xiaoxiaotank/