欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#中逆變的實(shí)際應(yīng)用場(chǎng)景詳解

 更新時(shí)間:2022年01月19日 10:35:36   作者:TinyMaD  
在好多的.net的書(shū)籍中都看到過(guò)逆變和協(xié)變的概念,也在網(wǎng)上搜了一些關(guān)于這兩個(gè)概念的解釋,但是一直感覺(jué)似懂非懂的,直到最近在項(xiàng)目中實(shí)際遇到了一個(gè)問(wèn)題,恰好用到了逆變,下面這篇文章主要給大家介紹了關(guān)于C#中逆變的實(shí)際應(yīng)用場(chǎng)景,需要的朋友可以參考下

前言

早期在學(xué)習(xí)泛型的協(xié)變與逆變時(shí),網(wǎng)上的文章講解、例子算是能看懂,但關(guān)于逆變的具體應(yīng)用場(chǎng)景這方面的知識(shí),我并沒(méi)有深刻的認(rèn)識(shí)。
本文將在具體的場(chǎng)景下,從泛型接口設(shè)計(jì)的角度出發(fā),逐步探討逆變的作用,以及它能幫助我們解決哪方面的問(wèn)題?

這篇文章算是協(xié)變、逆變知識(shí)的感悟和分享,開(kāi)始之前,你應(yīng)該先了解協(xié)變、逆變的基本概念,以及依賴(lài)注入,這類(lèi)文章很多,這里就不再贅述。

協(xié)變的應(yīng)用場(chǎng)景

雖然協(xié)變不是今天的主要內(nèi)容,但在此之前,我還是想提一下關(guān)于協(xié)變的應(yīng)用場(chǎng)景。

其中最常見(jiàn)的應(yīng)用場(chǎng)景就是——如果方法的某個(gè)參數(shù)是一個(gè)集合時(shí),我習(xí)慣將這個(gè)集合參數(shù)定義為IEnumerable<T>類(lèi)型。

class Program
{
    public static void Save(IEnumerable<Animal> animals)
    {
        // TODO
    }
}
public class Animal { }

IEnumerable<T>中的T就是標(biāo)記了代表協(xié)變的關(guān)鍵字out

namespace System.Collections.Generic
{
    public interface IEnumerable<out T> : IEnumerable
    {
        IEnumerator<T> GetEnumerator();
    }
}

假如泛型T為父類(lèi)Animal類(lèi)型,DogAnimal的子類(lèi),其他人在調(diào)用這個(gè)方法時(shí),

不僅可以傳入IEnumerable<Animal>List<Animal>、Animal[]類(lèi)型的參數(shù),

還可以傳入IEnumerable<Dog>List<Dog>、Dog[]等其他繼承自IEnumerable<Animal>類(lèi)型的參數(shù)。

這樣,方法的兼容性會(huì)更強(qiáng)。

class Program
{
    public static void Save(IEnumerable<Animal> animals)
    {
        // TODO
    }

    static void Main(string[] args)
    {
        var animalList = new List<Animal>();
        var animalArray = new Animal[] { };
        var dogList = new List<Dog>();
        var dogArray = new Dog[] { };

        Save(animalList);
        Save(animalArray);
        Save(dogList);
        Save(dogArray);
    }
}
public class Animal { }
public class Dog : Animal { }

逆變的應(yīng)用場(chǎng)景

提起逆變,可能大家見(jiàn)過(guò)類(lèi)似下面這段代碼:

class Program
{
    static void Main(string[] args)
    {
        IComparer<Animal> animalComparer = new AnimalComparer();
        IComparer<Dog> dogComparer = animalComparer;// 將 IComparer<Animal> 賦值給 IComparer<Dog>
    }
}

public class AnimalComparer : IComparer<Animal>
{
    // 省略具體實(shí)現(xiàn)
}

IComparer<T>中的T就是標(biāo)記了代表逆變的關(guān)鍵字in

namespace System.Collections.Generic
{
    public interface IComparer<in T>
    {
        int Compare(T? x, T? y);
    }
}

在看完這段代碼后,不知道你們是否跟我有一樣的想法:道理都懂,可是具體的應(yīng)用場(chǎng)景呢?

要探索逆變可以幫助我們解決哪些問(wèn)題,我們?cè)囍鴱牧硪粋€(gè)角度出發(fā)——在某個(gè)場(chǎng)景下,不使用逆變,是否會(huì)遇到某些問(wèn)題。

假設(shè)我們需要保存各種基礎(chǔ)資料,根據(jù)需求我們定義了對(duì)應(yīng)的接口,以及完成了對(duì)應(yīng)接口的實(shí)現(xiàn)。這里假設(shè)AnimalHuman就是其中的兩種基礎(chǔ)資料類(lèi)型。

public interface IAnimalService
{
    void Save(Animal entity);
}
public interface IHumanService
{
    void Save(Human entity);
}

public class AnimalService : IAnimalService
{
    public void Save(Animal entity)
    {
        // TODO
    }
}

public class HumanService : IHumanService
{
    public void Save(Human entity)
    {
        // TODO
    }
}

public class Animal { }
public class Human { }

現(xiàn)在增加一個(gè)批量保存基礎(chǔ)資料的功能,并且實(shí)時(shí)返回保存進(jìn)度。

public class BatchSaveService
{
    private static readonly IAnimalService _animalSvc;
    private static readonly IHumanService _humanSvc;
    // 省略依賴(lài)注入代碼

    public void BatchSaveAnimal(IEnumerable<Animal> entities)
    {
        foreach (var animal in entities)
        {
            _animalSvc.Save(animal);
            // 省略監(jiān)聽(tīng)進(jìn)度代碼
        }
    }
    public void BatchSaveHuman(IEnumerable<Human> entities)
    {
        foreach (var human in entities)
        {
            _humanSvc.Save(human);
            // 省略監(jiān)聽(tīng)進(jìn)度代碼
        }
    }
}

完成上面代碼后,我們可以發(fā)現(xiàn),監(jiān)聽(tīng)進(jìn)度的代碼寫(xiě)了兩次,如果像這樣的基礎(chǔ)資料類(lèi)型很多,想要修改監(jiān)聽(tīng)進(jìn)度的代碼,則會(huì)牽一發(fā)而動(dòng)全身,這樣的代碼就不便于維護(hù)。

為了使代碼能夠復(fù)用,我們需要抽象出一個(gè)保存基礎(chǔ)資料的接口ISave<T>。

使IAnimalService、IHumanService繼承ISave<T>,將泛型T分別定義為Animal、Human

public interface ISave<T>
{
    void Save(T entity);
}

public interface IAnimalService : ISave<Animal> { }
public interface IHumanService : ISave<Human> { }

這樣,就可以將BatchSaveAnimal()BatchSaveHuman()合并為一個(gè)BatchSave<T>()

public class BatchSaveService
{
    private static readonly IServiceProvider _svcProvider;
    // 省略依賴(lài)注入代碼

    public void BatchSave<T>(IEnumerable<T> entities)
    {
        ISave<T> service = _svcProvider.GetRequiredService<ISave<T>>();// GetRequiredService()會(huì)在無(wú)對(duì)應(yīng)接口實(shí)現(xiàn)時(shí)拋出錯(cuò)誤

        foreach (T entity in entities)
        {
            service.Save(entity);
            // 省略監(jiān)聽(tīng)進(jìn)度代碼
        }
    }
}

重構(gòu)后的代碼達(dá)到了可復(fù)用、易維護(hù)的目的,但很快你會(huì)發(fā)現(xiàn)新的問(wèn)題。

在調(diào)用重構(gòu)后的BatchSave<T>()時(shí),傳入Human類(lèi)型的集合參數(shù),或Animal類(lèi)型的集合參數(shù),代碼能夠正常運(yùn)行,但在傳入Dog類(lèi)型的集合參數(shù)時(shí),代碼運(yùn)行到第8行就會(huì)報(bào)錯(cuò),因?yàn)槲覀儾](méi)有實(shí)現(xiàn)ISave<Dog>接口。

雖然DogAnimal的子類(lèi),但卻不能使用保存Animal的方法,這肯定會(huì)被接口調(diào)用者吐槽,因?yàn)樗环?strong>里氏替換原則。

static void Main(string[] args)
{
    List<Human> humans = new() { new Human() };
    List<Animal> animals = new() { new Animal() };
    List<Dog> dogs = new() { new Dog() };

    var saveSvc = new BatchSaveService();

    saveSvc.BatchSave(humans);
    saveSvc.BatchSave(animals);
    saveSvc.BatchSave(dogs);// 由于沒(méi)有實(shí)現(xiàn)ISave<Dog>接口,因此代碼運(yùn)行時(shí)會(huì)報(bào)錯(cuò)
}

TDog時(shí),要想獲取ISave<Animal>這個(gè)不相關(guān)的服務(wù),我們可以從IServiceCollection服務(wù)集合中去找。

雖然我們拿到了注冊(cè)的所有服務(wù),但如何才能在TDog類(lèi)型時(shí),拿到對(duì)應(yīng)的ISave<Animal>服務(wù)呢?

這時(shí),逆變就派上用場(chǎng)了,我們將接口ISave<T>加上關(guān)鍵字in后,就可以將ISave<Animal>分配給ISave<Dog>

public interface ISave<in T>// 加上關(guān)鍵字in
{
    void Save(T entity);
}

public class BatchSaveService
{
    private static readonly IServiceProvider _svcProvider;
    private static readonly IServiceCollection _svcCollection;
    // 省略依賴(lài)注入代碼

    public void BatchSave<T>(IEnumerable<T> entities)
    {
        // 假設(shè)T為Dog,只有在ISave<T>接口標(biāo)記為逆變時(shí),
        // typeof(ISave<Animal>).IsAssignableTo(typeof(ISave<Dog>)),才會(huì)是true
        Type serviceType = _svcCollection.Single(x => x.ServiceType.IsAssignableTo(typeof(ISave<T>))).ServiceType;

        ISave<T> service = _svcProvider.GetRequiredService(serviceType) as ISave<T>;// ISave<Animal> as ISave<Dog>

        foreach (T entity in entities)
        {
            service.Save(entity);
            // 省略監(jiān)聽(tīng)進(jìn)度代碼
        }
    }
}

現(xiàn)在BatchSave<T>()算是符合里氏替換原則,但這樣的寫(xiě)法也有缺點(diǎn)

  • 優(yōu)點(diǎn):調(diào)用時(shí),寫(xiě)法干凈簡(jiǎn)潔,不需要設(shè)置過(guò)多的泛型參數(shù),只需要傳入對(duì)應(yīng)的參數(shù)變量即可。

  • 缺點(diǎn):如果傳入的參數(shù)沒(méi)有對(duì)應(yīng)的接口實(shí)現(xiàn),編譯仍然會(huì)通過(guò),只有在代碼運(yùn)行時(shí)才會(huì)報(bào)錯(cuò),提示不夠積極、友好。
    并且如果我們實(shí)現(xiàn)了ISave<Dog>接口,那代碼運(yùn)行到第16行時(shí)會(huì)得到ISave<Dog>ISave<Animal>兩個(gè)結(jié)果,不具有唯一性。

要想在錯(cuò)誤使用接口時(shí),編譯器及時(shí)提示錯(cuò)誤,可以將接口重構(gòu)成下面這樣

public class BatchSaveService
{
    private static readonly IServiceProvider _svcProvider;
    // 省略依賴(lài)注入代碼

    // 增加一個(gè)泛型參數(shù)TService,用來(lái)指定調(diào)用哪個(gè)服務(wù)的Save()
    // 并約定 TService : ISave<T>
    public void BatchSave<TService, T>(IEnumerable<T> entities) where TService : ISave<T>
    {
        ISave<T> service = _svcProvider.GetService<TService>();
        foreach (T entity in entities)
        {
            service.Save(entity);
            // 省略監(jiān)聽(tīng)進(jìn)度代碼
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        List<Human> humans = new() { new Human() };
        List<Animal> animals = new() { new Animal() };
        List<Dog> dogs = new() { new Dog() };
    
        var saveSvc = new BatchSaveService();

        saveSvc.BatchSave<IHumanService, Human>(humans);
        saveSvc.BatchSave<IAnimalService, Animal>(animals);
        saveSvc.BatchSave<IAnimalService, Dog>(dogs);
        // 假如實(shí)現(xiàn)了繼承ISave<Dog>的接口IDogService,可以改為
        // saveSvc.BatchSave<IDogService, Dog>(dogs);
    }
}

這樣在錯(cuò)誤使用接口時(shí),編譯器就會(huì)及時(shí)報(bào)錯(cuò),但由于需要設(shè)置多個(gè)泛型參數(shù),使用起來(lái)會(huì)有些麻煩。

關(guān)于 C# 協(xié)變和逆變 msdn 解釋如下: 

“協(xié)變”是指能夠使用與原始指定的派生類(lèi)型相比,派生程度更大的類(lèi)型。 

“逆變”則是指能夠使用派生程度更小的類(lèi)型。 

解釋的很正確,大致就是這樣,不過(guò)不夠直白。 

直白的理解: 

“協(xié)變”->”和諧的變”->”很自然的變化”->string->object :協(xié)變。 

“逆變”->”逆常的變”->”不正常的變化”->object->string 逆變。 

上面是個(gè)人對(duì)協(xié)變和逆變的理解,比起記住那些派生,類(lèi)型,原始指定,更大,更小之類(lèi)的詞語(yǔ),個(gè)人認(rèn)為要容易點(diǎn)。 

討論

以上是我遇見(jiàn)的比較常見(jiàn)的關(guān)于逆變的應(yīng)用場(chǎng)景,上述兩種方式你覺(jué)得哪種更好?是否有更好的設(shè)計(jì)方式?或者大家在寫(xiě)代碼時(shí)遇見(jiàn)過(guò)哪些逆變的應(yīng)用場(chǎng)景?

總結(jié)

到此這篇關(guān)于C#中逆變實(shí)際應(yīng)用場(chǎng)景的文章就介紹到這了,更多相關(guān)C# 逆變應(yīng)用場(chǎng)景內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 總結(jié)C#刪除字符串?dāng)?shù)組中空字符串的幾種方法

    總結(jié)C#刪除字符串?dāng)?shù)組中空字符串的幾種方法

    C#中要如何才能刪除一個(gè)字符串?dāng)?shù)組中的空字符串呢?下面的文章會(huì)介紹多種方式來(lái)實(shí)現(xiàn)清除數(shù)組中的空字符串,以及在.net中將字符串?dāng)?shù)組中字符串為空的元素去除。
    2016-08-08
  • C#詞法分析器之構(gòu)造NFA詳解

    C#詞法分析器之構(gòu)造NFA詳解

    本篇文章介紹了,C#詞法分析器之構(gòu)造NFA詳解。需要的朋友參考下
    2013-05-05
  • c# 使用OpenCV識(shí)別硬幣

    c# 使用OpenCV識(shí)別硬幣

    這篇文章主要介紹了c# 使用OpenCV識(shí)別硬幣的方法,幫助大家更好的利用c#進(jìn)行深度學(xué)習(xí),感興趣的朋友可以了解下
    2020-12-12
  • C#使用foreach遍歷哈希表(hashtable)的方法

    C#使用foreach遍歷哈希表(hashtable)的方法

    這篇文章主要介紹了C#使用foreach遍歷哈希表(hashtable)的方法,是C#中foreach語(yǔ)句遍歷散列表的典型應(yīng)用,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-04-04
  • C#獲取Visio模型信息的簡(jiǎn)單方法示例

    C#獲取Visio模型信息的簡(jiǎn)單方法示例

    這篇文章主要給大家介紹了關(guān)于C#獲取Visio模型信息的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • C# WCF簡(jiǎn)單入門(mén)圖文教程(VS2010版)

    C# WCF簡(jiǎn)單入門(mén)圖文教程(VS2010版)

    這篇文章主要介紹了WCF簡(jiǎn)單入門(mén)圖文教程,版本是VS2010版,幫助大家輕松學(xué)習(xí)了解DataContract、ServiceContract等特性,感興趣的小伙伴們可以參考一下
    2016-03-03
  • C#中的那些警告該如何去除(完全去除C#警告)

    C#中的那些警告該如何去除(完全去除C#警告)

    C#(英文名為 CSharp)是微軟開(kāi)發(fā)的一種面向?qū)ο蟮木幊陶Z(yǔ)言,下面這篇文章主要給大家介紹了關(guān)于C#中的那些警告該如何去除的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02
  • c# 修改windows中賬戶(hù)的用戶(hù)名和密碼

    c# 修改windows中賬戶(hù)的用戶(hù)名和密碼

    這篇文章主要介紹了c# 改變windows中賬戶(hù)的用戶(hù)名和密碼,幫助大家更好的理解和學(xué)習(xí)C#,感興趣的朋友可以了解下
    2020-11-11
  • C#基于WebSocket實(shí)現(xiàn)聊天室功能

    C#基于WebSocket實(shí)現(xiàn)聊天室功能

    這篇文章主要為大家詳細(xì)介紹了C#基于WebSocket實(shí)現(xiàn)聊天室功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • C# 超高面試題收集整理

    C# 超高面試題收集整理

    C# 超高面試題,學(xué)習(xí)c sharp的朋友可以看下,有說(shuō)明地方的不足。是不是所有問(wèn)題都有自己的解決方法。
    2010-03-03

最新評(píng)論