Autofac之自動裝配

從容器中的可用服務中選擇一個構造函數來創造對象,這個過程叫做自動裝配。這個過程是通過反射實現的

默認

思考這麼一個問題,如果註冊類型中存在多個構造函數,那麼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的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; } 
}
複製代碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章