C#中的協變與逆變小結

一:什麼是協變與逆變

協變指能夠使用比原始指定的派生類型的派生程度更大(更具體的)的類型,逆變指能夠使用比原始指定的派生類型的派生程度更小(不太具體的)的類型

隻有泛型接口和泛型委托參數支持協變和逆變

二:引言

using System;
using System.Collections.Generic;
 
class MainClass
{
    static void Main()
    {
        object o = "str";
        List<object> oList = new List<string>();
        IEnumerable<object> strs = new List<string>();
    }
}

上面這段代碼中,第一句沒問題,屬於類型安全轉換,第二句會報錯,因為這兩個list並沒有繼承關系,而第三句是正確的,其實在背後,就是協變和逆變在起作用

三:協變

協變在泛型方法的參數裡以out表示,使用out可以在聲明父類泛型參數的時候使用子類泛型參數構造,out參數可以單純的理解為輸出,作為返回值例如IEnumerable<T>接口

using System;
using System.Collections.Generic;
 
class MainClass
{
    static void Main()
    {
        IEnumerable<object> list = new List<string>();
    }
}

分析一下上面的代碼為什麼是合法的呢?首先雖然是用IEnumerable<object>聲明的,但是是用List<string>構造的,列表中的元素是string類型。其次IEnumerable的作用隻有遍歷元素,不允許添加操作,所以是合法的,本質上就是裡氏替換原則

四:逆變

逆變在泛型方法的參數裡以in表示,使用in可以在聲明子類泛型參數的時候使用父類泛型參數構造,int參數隻能作為傳入值不能作為返回值例如Action<T>委托

using System;
 
class MainClass
{
    static void Main()
    {
        Action<string> action = new Action<object>((o)=> { });
        action("");
    }
}

分析一下上面的代碼為什麼是合法的呢?看似是object轉換成瞭string,但實際上使用委托的時候傳入的是一個string類型的參數,然後將string轉換成瞭object,本質上還是派生類到基類的轉換,所以是類型安全的,本質上就是裡氏替換原則

五:為什麼協變和逆變是針對泛型接口或泛型委托參數的?

不能針對泛型類?

由上可知,協變和逆變都是定義方法成員的(接口不能定義字段隻能定義成員),而方法成員在創建對象時是不涉及到對象內存分配的,所以是類型安全的,而泛型類是模板類,類中可以包含字段, 所以是不安全的

using System;
using System.Collections.Generic;
 
class MainClass
{
    static void Main()
    {
        object o1 = "";//類型安全
        string s1 = (string)o1;//非類型安全
        IEnumerable<object> o2 = new List<string>();//協變
        Action<string> s2 = new Action<object>((o) => { });//逆變
    }
}

六:自定義協變

using System;
using System.Collections.Generic;
 
class MainClass
{
    static void Main()
    {
        ICustomCovariant<object> o = new CustomCovariant<string>();
    }
}
 
public interface ICustomCovariant<out T>
{
    T Get();
}
public class CustomCovariant< T> : ICustomCovariant<T>
{
    public T Get()
    {
        return default(T);
    }
}

七:自定義逆變

using System;
using System.Collections.Generic;
 
class MainClass
{
    static void Main()
    {
        IContravariant<string> o = new CustomContravariant<object>();
    }
}
 
public interface IContravariant<in T>
{
    void Get(T t);
}
public class CustomContravariant<T> : IContravariant<T>
{
    public void Get(T t)
    {
 
    }
}

八:總結

——在泛型中,如果確定泛型參數是隻讀或者隻寫的,那麼就可以使用協變或者逆變。如果泛型參數無法確定隻讀或隻寫,這種類型參數既不能協變也不能逆變,隻能精確類型匹配 ——在泛型或委托中,如果不使用協變或逆變,那麼泛型類型是一個固定類型,而使用協變或逆變的話,則泛型類型可以實現多態化

到此這篇關於C#中的協變與逆變的文章就介紹到這瞭,更多相關C#協變與逆變內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: