手把手教你寫DI_3_小白徒手支援 `Singleton` 和 `Sc

neuyu發表於2021-09-09

手把手教你寫DI_3_小白徒手支援 SingletonScoped 生命週期

在上一節:

渾身繃帶的小白同學:我們繼續開展我們的工作,大家都知道 Singleton是什麼,就是全域性只有一個唄,我們就先從它開始,這個多簡單,我們找個字典放這些物件就ok啦

public class ServiceProvider : IServiceProvider{
    ...    private readonly ConcurrentDictionary<Type, object> singletonCache = new ConcurrentDictionary<Type, object>();    public object GetService(Type serviceType)    {        case Lifetime.Singleton:
                singletonCache.GetOrAdd(serviceType, x => 
                {                    if(defintion is DelegateServiceDefintion defi)
                    {                        return defi.ImplementationFactory(this);
                    }                    else
                    {
                        ConstructorInfo constructor = cache.GetOrAdd(serviceType, i => 
                        {                            var d = defintion as TypeServiceDefintion;                            var implementationType = serviceType.IsConstructedGenericType 
                                ? d.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments)
                                : d.ImplementationType;                            return implementationType.GetConstructors().FirstOrDefault(i => i.IsPublic);
                        });
                        ar ps = constructor.GetParameters();                        var args = new object[ps.Length];                        for (int j = 0; j < ps.Length; j++)
                        {                            var p = ps[j];
                            args[j] = i.GetService(p.ParameterType);  // 小白同學:  獲取引數值
                        }                        return constructor.Invoke(args);  // 小白同學:  建立;
                    }
                });

        ....        case Lifetime.Transient:
               .....
        ....
    }
}

大神:我的刀呢?

小白同學:我錯啦!!!

public class ServiceProvider : IServiceProvider{
    ...    private readonly ConcurrentDictionary<Type, object> singletonCache = new ConcurrentDictionary<Type, object>();    public object GetService(Type serviceType)    {        case Lifetime.Singleton:             return singletonCache.GetOrAdd(serviceType, x => CreateObj(x));        case Lifetime.Scoped:             return CreateObj(x);        case Lifetime.Transient:             return CreateObj(x);
        ....
    }    public object CreateObj(Type serviceType)    {        if(defintion is DelegateServiceDefintion defi)
        {            return defi.ImplementationFactory(this);
        }        else
        {
            ConstructorInfo constructor = cache.GetOrAdd(serviceType, i => 
            {                var d = defintion as TypeServiceDefintion;                var implementationType = serviceType.IsConstructedGenericType 
                    ? d.ImplementationType.MakeGenericType(serviceType.GenericTypeArguments)
                    : d.ImplementationType;                return implementationType.GetConstructors().FirstOrDefault(i => i.IsPublic);
            });
            ar ps = constructor.GetParameters();            var args = new object[ps.Length];            for (int j = 0; j < ps.Length; j++)
            {                var p = ps[j];
                args[j] = i.GetService(p.ParameterType);  // 小白同學:  獲取引數值
            }            return constructor.Invoke(args);  // 小白同學:  建立;
        }
    }

            
}

小白同學:好了,我們來說下 Scoped 作用域,百度百科的解釋是這樣的: 作用域(scope),程式設計概念,通常來說,一段程式程式碼中所用到的名字並不總是有效/可用的,而限定這個名字的可用性的程式碼範圍就是這個名字的作用域。
作用域的使用提高了程式邏輯的區域性性,增強程式的可靠性,減少名字衝突。
對於物件而言(其他也是一樣的),在main函式中,物件的作用域為他所在的最近的一對花括號內。在後花括號處解構函式被呼叫;全域性的物件的作用域為宣告之後的整個檔案,解構函式在最後被呼叫。另外,臨時產生的物件在使用完後立即會被析構。

小白同學:雖然比較奇怪為啥百度百科強調的是名字,名字不過是我們方便自己對應以及找到變數/記憶體地址等的手段而已。不過不管啦,反正DI裡面的Scoped概念和這段解釋有點點相似,是為DI提供將物件生命週期控制在自定義的範圍內部的一個手段,比如我們保證http 一次請求的生命週期內,一些比如context之類的處理,我們就可以用這樣的作用域概念處理,

小白同學:作用域由於考慮到不是我們自己控制,這是有使用者自定的,所以我們需要提供一些抽象介面讓使用者可以使用。這裡呢,我們就偷懶啦,抄襲一下別人的定義

public interface IServiceScopeFactory{    IServiceProvider CreateScopeProvider();
}

小白同學:我們來實現它

public class ServiceScopeFactory : IServiceScopeFactory{    public IServiceProvider CreateScopeProvider()    {        return new ServiceProvider();
    }
}

小白同學:大家看,多簡單,完美

大神:你問過我的青龍偃月刀了嗎?

小白同學(尷尬): 哈哈,怎麼可能寫完了,我是開個玩笑,肯定要把服務定義給過去

public class ServiceScopeFactory : IServiceScopeFactory{    private readonly IServiceDefintions services;    public ServiceScopeFactory(IServiceDefintions services)    {        this.services = services;
    }    public IServiceProvider CreateScopeProvider()    {        return new ServiceProvider(services);
    }
}

青龍偃月刀:你希望你的生命週期也和這個ServiceScopeFactory一樣無處安放嗎?

小白同學:為啥?我這不是實現了嗎?

青龍偃月刀:ServiceScopeFactory 使用者從哪裡拿?

小白同學:我放進ServiceDefintions呀,

var a = new ServiceDefintions();
a.Add(new DelegateServiceDefintion(typeof(IServiceScopeFactory),typeof(ServiceScopeFactory),Lifetime.Transient, i => new ServiceScopeFactory(a)));

青龍偃月刀:hehe, ServiceProviderIServiceScopeFactory 建立的都是新的吧?

小白同學:對,就是這樣,才能保證是新的作用域呀

青龍偃月刀:hehe, 那新的 ServiceProvider 建立的物件也是新的吧?

小白同學:對,就是這樣,新的作用域建立的物件肯定和舊的作用域建立的物件肯定不一樣

青龍偃月刀:hehe, 那Singleton不是全域性唯一嗎?

小白同學:啥?Singleton和作用域有什麼關係?我不是有字典快取了嗎?

青龍偃月刀:我真恨不得自己再把自己磨快點。

青龍偃月刀:ServiceProvider 是不是可以建立 三種不同生命週期的物件?

小白同學:對,Singleton ,Scoped, Transient

青龍偃月刀:那新的ServiceProvider建立的Singleton物件呢?

小白同學:都是從快取字典private readonly ConcurrentDictionary<Type, object> singletonCache 裡面拿唄

青龍偃月刀:。。。。。。 這個字典你放哪呢?

小白同學:我放ServiceProvider類上啊

青龍偃月刀:。。。。。。 那每一個新的ServiceProvider是不是都有一個新的快取字典?

小白同學:吃驚.gif, 不愧是寶刀

小白同學:我換靜態的 static ConcurrentDictionary<Type, object> singletonCache

青龍偃月刀:那整個程式就只有一份了啊

小白同學:對呀,就是隻要一份

青龍偃月刀:那一個程式裡面多個DI容器呢?

小白同學:大吃一驚.gif,還能這麼玩?

青龍偃月刀:不說其他,就說你單元測試一個DI容器能測試各種場景?

小白同學:尷尬.gif 我目前只寫了一個

青龍偃月刀:...............你改吧

小白同學:哦

// 小白同學:在IServiceProvider介面上新增我們需要資料欄位public interface IServiceProvider{
    Dictionary<Type, ServiceDefintion> Services {get;}
    ConcurrentDictionary<Type, object> SingletonCache {get;}
}public class ServiceProvider : IServiceProvider{    public Dictionary<Type, ServiceDefintion> Services {get;}    public ConcurrentDictionary<Type, object> SingletonCache {get;}    // 小白同學:複用對應的快取
    public ServiceProvider(IServiceProvider provider)    {
        Services = provider.Services;
        SingletonCache = provider.SingletonCache;
    }
}public class ServiceScopeFactory : IServiceScopeFactory{    private readonly IServiceProvider provider;    // 小白同學:這樣我們可以直接取已有的provider
    public ServiceScopeFactory(IServiceProvider provider)    {        this.provider = provider;
    }    public IServiceProvider CreateScopeProvider()    {         // 小白同學:有了存在的provider,我們就能複用對應的快取
        return new ServiceProvider(provider);
    }
}

小白同學:我們就可以這樣註冊ServiceScopeFactory

var a = new ServiceDefintions();
a.Add(new DelegateServiceDefintion(typeof(IServiceScopeFactory),typeof(ServiceScopeFactory),Lifetime.Transient, i => new ServiceScopeFactory(i)));

青龍偃月刀:磨刀石呢?我要磨快點

小白同學:又咋了,我寫的這麼完美?

青龍偃月刀:你確定這樣符合作用域的概念?

小白同學:怎麼不符合了?SingletonCache 都只有一個了,每個ServiceProvider都是建立新的Scoped生命週期物件

青龍偃月刀:你看看你是怎麼寫建立新的Scoped生命週期物件的?

小白同學:這樣啊

        case Lifetime.Scoped:             return CreateObj(x);

青龍偃月刀:一個Scoped生命週期內,一個ServiceType對應生成物件不該唯一嗎?

小白同學:為啥啊?生命週期不是使用者自己控制了嗎?

青龍偃月刀:一個方法的作用域內,可以宣告多個同名物件嗎?

小白同學:不能呀

青龍偃月刀:那你允許一個Scoped作用域內,可以生成相同ServiceType,實際不同的物件?

小白同學:他可以自己回收唄

青龍偃月刀:你讓人家自己回收 !!!??? 那人家為什麼不用Transient,你這樣和Transient有什麼區別?

小白同學:你說的好有道理,我竟無言以對

小白同學:那我加快取

public class ServiceProvider : IServiceProvider{    private ConcurrentDictionary<Type, object> scopedCache = new ConcurrentDictionary<Type, object>();    public object CreateObj(Type serviceType)    {        case Lifetime.Scoped:             return scopedCache.GetOrAdd(serviceType, x => CreateObj(x));
    }
}

小白同學:怎麼樣?完美吧?

青龍偃月刀:我勸你好好考慮一下,我的大刀已經飢渴難耐

小白同學:哪兒不完美?明明很beautiful

青龍偃月刀:再提示一下,使用者是不是會這樣用?

IServiceProvider a = IServiceScopeFactory.CreateScopeProvider();

doSomethings(a);

a.Dispose();

小白同學:對呀,可以完美應對呀

青龍偃月刀:。。。。。。。。。你的Dispose做了什麼?

小白同學:emmmm 什麼。。。 都沒做?

青龍偃月刀:那使用者Dispose什麼?

小白同學:emmmm。。。。。。

小白同學:好吧,既然有問題我們再改下

public class ServiceProvider : IServiceProvider{    private void Dispose()    {        // 小白同學:Dispose every thing
        foreach (var item in SingletonCache.Union(scopedCache))
        {            var disposable = item as IDisposable;
            disposable?.Dispose();
        }
        scopedCache.Clear();
        SingletonCache.Clear();
    }
}

青龍偃月刀:........... 一個子作用域可以把SingletonCache Dispose 了?難道活到98歲不好嗎?

小白同學:啊。。。。。活到那麼久很好啊。。。。哈,我知道怎麼改

public class ServiceProvider : IServiceProvider{    public IServiceProvider Root { get; }    public ServiceProvider(IServiceProvider provider)    {
        Services = provider.Services;
        SingletonCache = provider.SingletonCache;
        Root = provider.Root;
    }    private void Dispose()    {        // 小白同學:only Root can Dispose every thing
        //          others can onlu disposable scopedCache
        var disposables = (Root == this
            ? SingletonCache.Union(scopedCache)
            : scopedCache)
            .Where(x => x.Value != this);        foreach (var scoped in disposables)
        {            var disposable = scoped.Value as IDisposable;
            disposable?.Dispose();
        }
        scopedCache.Clear();        if (Root == this) SingletonCache.Clear();
    }
}

小白同學:真完美!!!!!

原文出處:https://www.cnblogs.com/fs7744/p/9931141.html  

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4550/viewspace-2816769/,如需轉載,請註明出處,否則將追究法律責任。

相關文章