前言
Autofac是一套高效的依賴注入框架。
Autofac官方網站:http://autofac.org/
Autofac在Github上的開源項目:https://github.com/autofac/Autofac
Autofac安裝:通過VS的Nuget可以很方便的獲取。
解析獲取方式
Resolve
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<Class_1>(); //如果注釋掉這句,下面Resolve時將會拋出異常 IContainer container = builder.Build(); Class_1 clas1 = container.Resolve<Class_1>(); Console.WriteLine(clas1.Id); Console.Write("Press any key to continue..."); Console.ReadKey(); } }class Class_1 { public Guid Id { get; set; } public Class_1() { Id = Guid.NewGuid(); } }這種方式在上一篇 Autofac全面解析系列(版本:3.5) – [使用篇(推薦篇):1.類型注冊] 的簡單示例中有使用到,這種方式在類型已經注冊的情況下使用時沒問題的,能夠獲取到注冊類型的實例對象,但是如果類型沒有經過注冊,直接Resolve解析獲取,便會拋出異常。
ResolveOptional
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); //builder.RegisterType<Class_1>(); //這里注釋掉類型注冊的代碼 IContainer container = builder.Build(); Class_1 clas1 = container.ResolveOptional<Class_1>(); Console.WriteLine(clas1 == null ? "null" : clas1.Id.ToString()); //這里將會輸出null Console.Write("Press any key to continue..."); Console.ReadKey(); } }使用Resolve時,如果類型沒有經過注冊,則會拋出異常,有時我們並不希望程序因此而拋出異常,我們可以使用ResolveOptional進行解析獲取,當類型沒有經過注冊時,ResolveOptional方法將會返回null作為結果。
TryResolve
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); //builder.RegisterType<Class_1>(); //這里注釋掉類型注冊的代碼 IContainer container = builder.Build(); Class_1 clas1 = null; if (container.TryResolve<Class_1>(out clas1)) { Console.WriteLine(clas1.Id); } else {//這里將會被執行 Console.WriteLine("null"); } Console.Write("Press any key to continue..."); Console.ReadKey(); } }這種方式相信大家並不陌生,這種方式與我們常用的Int32.TryParse相同。使用out參數,並且返回一個bool類型表示是否成功獲取到類型實例。
其他相關內容
Resolve對象構造方法選擇原則
當我們注冊的類型擁有多個構造方法,那么在Resolve時,將會以哪個構造方法為准呢?答案是——盡可能最多參數,下面我們以實例來分析:
class ConstructorClass { private Class1 _clas1; private Class2 _clas2; private Class3 _clas3 = null; public ConstructorClass() { _clas1 = null; _clas2 = new Class2 { Id = -1 }; } public ConstructorClass(Class1 clas1, Class2 clas2) { _clas1 = clas1; _clas2 = clas2; } public ConstructorClass(Class2 clas2, Class3 clas3) { _clas2 = clas2; _clas3 = clas3; } public ConstructorClass(Class2 clas2, Class3 clas3, Guid guid) { _clas1 = new Class1 {Id = guid}; _clas2 = clas2; _clas3 = clas3; } public ConstructorClass(Class1 clas1, Class2 clas2, Class3 clas3) { _clas1 = clas1; _clas2 = clas2; _clas3 = clas3; } public override string ToString() { return string.Format( "{{Class1.Id: {0}, Class2.Id: {1}, Class3: {2}}}", _clas1 == null ? "null" : _clas1.Id.ToString(), _clas2 == null ? "null" : _clas2.Id.ToString(), _clas3 == null ? "null" : "not null"); } }class Class1 { public Guid Id { get; set; } } class Class2 { public int Id { get; set; } } class Class3 { }//感謝 @就是要說不 的反饋糾正,代碼已修正 //主程序 class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<ConstructorClass>(); builder.RegisterType<Class2>(); builder.RegisterType<Class3>(); var container = builder.Build(); var obj = container.Resolve<ConstructorClass>(); Console.WriteLine(obj); Console.Write("Press any key to continue..."); Console.ReadKey(); } } //構造方法測試類 class ConstructorClass { private Class1 _clas1; private Class2 _clas2; private Class3 _clas3 = null; public ConstructorClass() { _clas1 = null; _clas2 = new Class2 { Id = -1 }; } public ConstructorClass(Class1 clas1, Class2 clas2) { _clas1 = clas1; _clas2 = clas2; } public ConstructorClass(Class2 clas2, Class3 clas3) { _clas2 = clas2; _clas3 = clas3; } public ConstructorClass(Class2 clas2, Class3 clas3, Guid guid) { _clas1 = new Class1 { Id = guid }; _clas2 = clas2; _clas3 = clas3; } public ConstructorClass(Class1 clas1, Class2 clas2, Class3 clas3) { _clas1 = clas1; _clas2 = clas2; _clas3 = clas3; } public override string ToString() { return string.Format( "{{Class1.Id: {0}, Class2.Id: {1}, Class3: {2}}}", _clas1 == null ? "null" : _clas1.Id.ToString(), _clas2 == null ? "null" : _clas2.Id.ToString(), _clas3 == null ? "null" : "not null"); } } //構造方法參數類型 class Class1 { public Guid Id { get; set; } } //構造方法參數類型 class Class2 { public int Id { get; set; } } //構造方法參數類型 class Class3 { }上面的代碼,最終輸出結果為 {Class1.Id: null, Class2.Id: 0, Class3: not null} ,最終執行的是第三個構造方法(參數為 Class2, Class3 的)。
根據結果,我們再來理解一下之前說的”盡可能最多參數“,按照字面上里說明”最多參數“,那么理應執行的是最后一個構造方法或倒數第二個構造方法,但是為什么卻是第三個,這也就是為什么我要加“盡可能”三字了。
先拋開為什么執行的第三個構造方法,我們還是會有疑問”如果執行的是第三個構造方法,那么Class2和Class3參數分別賦的是什么值?值又是從哪兒來?“,這里就涉及到了后面會講到的構造注入。我們可以看到,在進行類型注冊時,我們是對Class2和Class3進行了注冊的,而ConstructorClass又是通過Autofac進行獲取的,所以Class2和Class3參數的值是由Autofac進行初始化賦值的,Class2和Class3沒有自定義構造方法,所以調用的是默認的空構造方法。
在知道Class2和Class3參數的初始化與賦值緣由后,我們再來看看之前的那個問題,為什么會執行第三個構造方法,其實現在就好明白了,因為最后兩個的構造方法,一個需要額外的Guid類型參數,另一個需要Class1類型參數,而這兩個類型又沒有經過注冊,如果調用這兩個構造方法,那么Auotofac將不知道應該賦何值給這兩個參數,所以Autofac最終選擇了第三個構造方法。
這里我們還需要注意一點,如果倒數第二個構造方法的Guid參數給上默認值,那么最后選擇的構造方法將會是這個構造方法。讓我們再次想想盡可能最多參數這句話就明白了:
public ConstructorClass(Class2 clas2, Class3 clas3, Guid guid = default(Guid)) { _clas1 = new Class1 { Id = guid }; _clas2 = clas2; _clas3 = clas3; }如果在上面改造了倒數第二個構造方法的基礎上繼續改造最后一個構造方法,將Class1參數也默認賦值為null,那么最后在Resolve獲取ConstructorClass實例時,將會拋出異常。因為在盡可能最多的原則上,出現了兩個最多參數的構造方法,Autofac不知道應該選擇哪個進行執行。異常信息告訴我們可以使用UsingConstructor來解決這個問題(關於UsingConstructor的用法,將在后續博文中進行說明)。
public ConstructorClass(Class2 clas2, Class3 clas3, Class1 clas1 = null) { _clas1 = clas1; _clas2 = clas2; _clas3 = clas3; }
解析獲取傳參
我們在上一節明白了Autofac在Resolve時對構造方法選擇的原則,盡可能最多的參數中的參數,可以是已經注冊的類型,或是賦給默認值,除了這兩種方式,還有一種方式是在Resolve時指定參數。
在上一節中,沒有添加默認值的情況下,最后執行的是第三個構造方法。我們可以通過在Resolve時傳參來選擇更多參數的構造方法:
var obj = container.Resolve<ConstructorClass>(new NamedParameter("guid", Guid.NewGuid())); //點擊查看上一節完整代碼這里僅僅只修改這Resolve的這一句代碼,其他代碼不變。在Resolve時傳入了一個NamedParameter,NamedParameter表示按名字匹配參數,上面的代碼表示,為參數名為guid的構造參數傳入了Guid.NewGuid值。
這段代碼最后執行的是第四個構造方法。因為第四個構造方法是能夠匹配的最多參數的構造方法(前兩個參數類型已注冊到Autofac中,最后一個參數由Resolve時傳入的NamedParameter傳入)。
Resolve的方法簽名為:Resolve<T>(this IComponmentContext context, params Parameter[] parameters)
第一個參數也就是我們使用的container,我們主要關注第二個參數——一個可變的Parameter數組,Parameter是一個抽象類,其中NamedParameter為Parameter的一個子類,除了NamedParameter,還有以下幾種子類拱Resolve時使用:
參數類型
參數說明
NamedParameter
根據名稱進行匹配 PositionalParameter
根據索引進行匹配,注意:起始索引為0 TypedParameter
根據類型進行匹配,注意:傳入多個相同類型的TypedParameter,所有該類型的參數都將采用第一個TypedParameter的值 ResolvedParameter
接收兩個Func參數,兩個Func簽名都接收兩個相同的參數ParameterInfo和IComponmentContext,第一個參數為參數的信息(常使用放射的朋友應該熟悉),第二個參數還是當做IContainer使用就好了。第一個Func的返回值為bool,表明當前這個ResolvedParameter是否使用當前匹配到的參數,如果返回true,則會執行第二個Func;第二個Func返回一個object對象,用於填充構造參數值。
下面有一個這些Parameter使用的示例供參考:
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<ParameterClass>(); var container = builder.Build(); container.Resolve<ParameterClass>( new NamedParameter("value", "namedParameter"), //匹配名字為value的參數 new TypedParameter(typeof (int), 1), //匹配類型為int的參數 new PositionalParameter(4, "positionalParameter"), //匹配第五個參數(注意,索引位置從0開始) new TypedParameter(typeof (int), -1), //這個將被拋棄,因為前面已經有一個類型為int的TypedParameter new ResolvedParameter( //第一個Func參數用於返回參數是否符合要求,這里要求參數是類,且命名空間不是System開頭,所以第四個參數將會匹配上 (pi, cc) => pi.ParameterType.IsClass && !pi.ParameterType.Namespace.StartsWith("System"), //第二個Func參數在第一個Func執行結果為true時執行,用於給參數賦值,也就是第四個參數的值為這個Func的執行結果 (pi, cc) => new Temp {Name = "resolveParameter"}) ); // 最后的輸出結果為: {x:1, y:1, value:'namedParameter', temp.Name:'resolveParameter', obj:'positionalParameter'} Console.Write("Press any key to continue..."); Console.ReadKey(); } } class ParameterClass { public ParameterClass(int x, int y, string value, Temp temp, object obj) { Console.WriteLine("{{x:{0}, y:{1}, value:'{2}', temp.Name:'{3}', obj:'{4}'}}", x, y, value, temp.Name, obj); } } class Temp { public string Name { get; set; } }class ParameterClass { public ParameterClass(int x, int y, string value, Temp temp, object obj) { Console.WriteLine("{{x:{0}, y:{1}, value:'{2}', temp.Name:'{3}', obj:'{4}'}}", x, y, value, temp.Name, obj); } }class Temp { public string Name { get; set; } }
尾述
本篇博文主要講述Autofac中比較常用也比較好用的解析獲取方式,並沒有把所有的解析方式都講述出來。
本篇博文的重點實際在於理解構造方法的優先原則,解析獲取的方式相對簡單易用,而獲取傳參在實際的編碼中應用較少,這點在現在還看不出來,因為當前還沒有講到Autofac在編程過程中的實際用法。后續博文將會慢慢道明,請大家繼續關注!
