從容器中的可用服務中選擇一個構造函數來創造對象,這個過程叫做自動裝配。這個過程是通過反射實現的
默認
思考這么一個問題,如果注冊類型中存在多個構造函數,那么Autofac會選擇哪一個來創建類型的實例
答案是"盡可能最多參數"
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 {}static void Main(string[] args)
static void Main(string[] args) {//注冊容器var builder = new ContainerBuilder();//向容器中注冊類型builder.RegisterType<ConstructorClass>();builder.RegisterType<Class2>();builder.RegisterType<Class3>();using (var container = builder.Build()){#region Resolve對象構造方法選擇原則(當我們注冊的類型擁有多個構造方法,那么在Resolve時,將會以盡可能最多參數構造方法為準)var obj = container.Resolve<ConstructorClass>();Console.WriteLine(obj);#endregion }Console.ReadKey(); }
該實例顯示,選擇的是第三個構造函數,參數為(Class2 clas2, Class3 clas3),
按照字面上里說明”最多參數“,那么理應執行的是最后一個構造方法或倒數第二個構造方法,但是為什么卻是第三個,這也就是為什么我要加“盡可能”三字了。
先拋開為什么執行的第三個構造方法,我們還是會有疑問”如果執行的是第三個構造方法,那么Class2和Class3參數分別賦的是什么值?值又是從哪兒來?“,這里就涉及到了后面會講到的構造注入。我們可以看到,在進行類型注冊時,我們是對Class2和Class3進行了注冊的,而ConstructorClass又是通過Autofac進行獲取的,所以Class2和Class3參數的值是由Autofac進行初始化賦值的,Class2和Class3沒有自定義構造方法,所以調用的是默認的空構造方法。
在知道Class2和Class3參數的初始化與賦值緣由后,我們再來看看之前的那個問題,為什么會執行第三個構造方法,其實現在就好明白了,因為最后兩個的構造方法,一個需要額外的Guid類型參數,另一個需要Class1類型參數,而這兩個類型又沒有經過注冊,如果調用這兩個構造方法,那么Auotofac將不知道應該賦何值給這兩個參數,所以Autofac最終選擇了第三個構造方法。
此時我把第三個構造函數注釋掉之后,會調用第一個構造函數,按照"盡可能最多參數"原則,此時不應該調用第二個嗎?答案是,Autofac會在已注冊的類型中尋找,雖然Class2類型被注冊,第二個構造函數Class1類型參數Autofac不知道如何賦值,所以選擇了默認的構造函數,如果在容器中注冊類型Class1取消掉類型Class3的注冊,此時就會調用第二個構造函數.(Autofac尋找構造函數的規則是在已注冊的類型中尋找參數完全匹配的構造函數)
UsingConstructor:指定使用某個構造函數
通過上面的例子我們知道Autofac創建類型實例時會默認從容器中選擇匹配參數最多的構造函數,此時在容器中將Class1、Class2、Class3類型都注冊,此時默認情況會使用最后一個構造函數,如果如果想要選擇一個不同的構造函數,就需要在注冊的時候就指定它,此時指定使用參數為(Class1 clas1, Class2 clas2)的構造函數
builder.RegisterType<Class1>(); builder.RegisterType<Class2>(); builder.RegisterType<Class3>();
builder.RegisterType<ConstructorClass>().UsingConstructor(typeof(Class1), typeof(Class2));
?額外的構造函數參數
有兩種方式可以添加額外的構造函數參數,在注冊的時候和在檢索的時候。在使用自動裝配實例的時候這兩種都會用到。
注冊時添加參數
使用WithParameters()方法在每一次創建對象的時候將組件和參數關聯起來。
builder.RegisterType<ConstructorClass>().WithParameter("guid", Guid.NewGuid()); //builder.RegisterType<Class1>();//將Class1注冊因為在盡可能最多的原則上,出現了兩個最多參數的構造方法,Autofac不知道應該選擇哪個進行執行 builder.RegisterType<Class2>(); builder.RegisterType<Class3>();
在檢索階段添加參數
在Resolve()的時候提供的參數會覆蓋所有名字相同的參數,在注冊階段提供的參數會覆蓋容器中所有可能的服務。
var obj = container.Resolve<ConstructorClass>(new NamedParameter("guid", Guid.NewGuid()));
?自動裝配
在需要的時候,依然可以創建指定的構造函數創建指定的類。
builder.Register(c => new Clas1());
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的TypedParameternew 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; } }