手把手教你寫DI_3_小白徒手支援 `Singleton` 和 `Scoped` 生命週期
手把手教你寫DI_3_小白徒手支援 Singleton
和 Scoped
生命週期
渾身繃帶的小白同學:我們繼續開展我們的工作,大家都知道 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
小白同學:作用域由於考慮到不是我們自己控制,這是有使用者自定的,所以我們需要提供一些抽象介面讓使用者可以使用。這裡呢,我們就偷懶啦,抄襲一下別人的定義
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, ServiceProvider
由 IServiceScopeFactory
建立的都是新的吧?
小白同學:對,就是這樣,才能保證是新的作用域呀
青龍偃月刀: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();
}
}
小白同學:真完美!!!!!