1. 程式人生 > 程式設計 >詳解c# 協變和逆變

詳解c# 協變和逆變

基本概念

協變:能夠使用比原始指定的派生型別的派生程度更大(更具體)的型別。例如 IFoo<父類> = IFoo<子類>
逆變:能夠使用比原始指定的派生型別的派生程度更新(更抽象)的型別。例如 IBar<子類> = IBar<父類>

關鍵字out和in

協變和逆變在泛型引數中的表現方式,out關鍵字表示協變,in關鍵字表示逆變。二者只能在泛型介面或者委託中使用。

理解協變和逆變

看完上面的定義是不是一臉懵逼~~~。看不懂就對了,且定義語句的歧義性很大。讓我們大腦趕緊清空下!!首先記住一點明確的概念,類的多型展示一定是通過基類來表示,派生的具體類都是可轉化為基類,而不能走相反的流程。

下面我們用程式碼直觀的表現下協變和逆變。

public class Animal
{
  public void Eat()
  { }
}

public class Dog : Animal
{
  public void Run()
  {
  }
}

這是一段很簡單的子類和父類的關係,我們進行一下簡單的轉化,應該很好理解,Dog子類可以用Animal父類展示,反過來則不可以,會編譯錯誤。

    Dog dog = new Dog();
    Animal animal = dog;

    //error 編譯錯誤
    //Dog dog2 = animal;

那麼我們做一點變化。

    List<Dog> dogs = new List<Dog>();
    //error 編譯錯誤
    //List<Animal> animals_2 = dogs;

    IEnumerable<Dog> dogs_2 = dogs;
    IEnumerable<Animal> animals = dogs_2;

感覺到一點問題沒?Dog子類可以用Animal父類展示,使用List泛型就不可以了,但是IEnumerable泛型又可以。List<>和IEnumerable<>有什麼不同?我們看下二者的定義即可發現端倪。

//IList定義
public interface IList<[NullableAttribute(2)] T> : ICollection<T>,IEnumerable<T>,IEnumerable
{}

//和IEnumerable定義
public interface IEnumerable<[NullableAttribute(2)] out T> : IEnumerable
{}

區別就在於 IEnumerable的泛型引數用了out協變標註,所以可以做正確的轉換。 這裡也可以理解出什麼時候需要使用in、out關鍵字:當你設計帶有泛型的基類且泛型型別可能存在擴充套件時,則需要考慮使用in或者out關鍵字修飾。
我們再看看官方的Action<>和Func<>類對協變和逆變的使用,先看定義:

public delegate void Action<[NullableAttribute(2)] in T>(T obj);

public delegate TResult Func<[NullableAttribute(2)] in T,[NullableAttribute(2)] out TResult>(T arg);

Action的泛型型別是入參,用in表示逆變,Func的第二個泛型型別TResult是出參,用out表示協變。
那麼這樣看起來對in、out關鍵字的認識就很簡單明瞭了。看看轉換示例:

    Action<Dog> action_dog = d => d.Run();
    Action<Animal> action_animal = a => a.Eat();

    //error 編譯錯誤。in
    //Action<Animal> action_animal_2 = action_dog;
		//Action泛型多型化
    Action<Dog> action_dog_2 = action_animal;

    Func<int,Dog> func_dog = a => { return new Dog(); };
    Func<int,Animal> func_animal = a => { return new Animal(); };

		//Func泛型多型化
    Func<int,Animal> func_animal_2 = func_dog;
    //error 編譯錯誤。out
    //Func<int,Dog> func_dog_2 = func_animal;

注意註釋編譯錯誤的語句,符合上面我們轉換的規則。對於入參,擴充套件類可以替代基類引數輸入,用in修飾;對於出參,擴充套件類可以替代基類返回輸出,用out修飾。相反則都不可以。

最後簡單總結下:

  • 什麼是協變/逆變?不要去想官方定義!!!!只要記住out是協變,in是逆變即可。
  • 為什麼需要使用協變-out、逆變-in。在泛型或委託中,如果不使用協變/逆變,那麼泛型型別一個精確的、固定的某一型別。而使用協變/逆變的話,則泛型型別可以實現多型化。但必須區分入參使用in,出參使用out。

以上就是詳解c# 協變和逆變的詳細內容,更多關於c# 協變和逆變的資料請關注我們其它相關文章!