天天看點

C#程式設計,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();
}
           
C#程式設計,Autofac之自動裝配

該執行個體顯示,選擇的是第三個構造函數,參數為(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>();
           

額外的構造函數參數

有兩種方式可以添加額外的構造函數參數,在注冊的時候和在檢索的時候。在使用自動裝配執行個體的時候這兩種都會用到。

注冊時添加參數

使用WithParameters()方法在每一次建立對象的時候将元件和參數關聯起來。

builder.RegisterType<ConstructorClass>().WithParameter("guid", Guid.NewGuid());
//builder.RegisterType<Class1>();//将Class1注冊因為在盡可能最多的原則上,出現了兩個最多參數的構造方法,Autofac不知道應該選擇哪個進行執行
builder.RegisterType<Class2>();
builder.RegisterType<Class3>();
           

在檢索階段添加參數

在Resolve()的時候提供的參數會覆寫所有名字相同的參數,在注冊階段提供的參數會覆寫容器中所有可能的服務。

自動裝配

在需要的時候,依然可以建立指定的構造函數建立指定的類。

Resolve的方法簽名為:Resolve(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](https://www.cnblogs.com/GnailGnepGnaw/p/10757340.html)>();

        var container = builder.Build();
        container.Resolve<[ParameterClass](https://www.cnblogs.com/GnailGnepGnaw/p/10757340.html)>(
            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](https://www.cnblogs.com/GnailGnepGnaw/p/10757340.html) {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; } 
}
           

原文連結: https://www.cnblogs.com/GnailGnepGnaw/p/10757340.html