Autofac全面解析系列(版本:3.5) – [使用篇(推薦篇):2.解析獲取]

前言

Autofac是一套高效的依賴注入框架。javascript

Autofac官方網站:http://autofac.org/html

Autofac在Github上的開源項目:https://github.com/autofac/Autofacjava

Autofac安裝:經過VS的Nuget能夠很方便的獲取。git

 

解析獲取方式

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解析獲取,便會拋出異常。github

 

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 的)。ide

根據結果,咱們再來理解一下以前說的」儘量最多參數「,按照字面上裏說明」最多參數「,那麼理應執行的是最後一個構造方法或倒數第二個構造方法,可是爲何倒是第三個,這也就是爲何我要加「儘量」三字了。測試

先拋開爲何執行的第三個構造方法,咱們仍是會有疑問」若是執行的是第三個構造方法,那麼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在編程過程當中的實際用法。後續博文將會慢慢道明,請你們繼續關注!

相關文章
相關標籤/搜索