前言
autofac
Autofac是一套高效的依賴注入框架。
Autofac官方網站:http://autofac.org/
Autofac在Github上的開源項目:https://github.com/autofac/Autofac
Autofac安裝:通過VS的Nuget可以很方便的獲取。
類型關聯(服務暴露)
關於“類型關聯(服務暴露)”這個名字,是源於官網上的exposes一詞,有點詞窮,希望大家在看完博客后能夠提供更加貼切的描述名稱。
前面的autofac系列文章一直有提到直接注冊類型並不是autofac已經依賴注入的主要使用方式,最佳的依賴注入與autofac的使用方式,都是要結合面向接口(抽象)編程的概念的。我們推崇的是依賴於抽象而不是具體,關於這點,我在上一篇博文最后的總結中有簡單的說明。
了解autofac的朋友在看前面的博文的時候,可能會覺得很別扭,因為沒有使用As<>。但是博主覺得依賴注入並不是一定要與接口或抽象類關聯在一起才能算依賴注入,博主是想將依賴注入與面向抽象編程稍稍分離開來,在大家理解依賴注入的概念后再來與面向抽象編程概念相結合,這樣對於初學者可能更方便吸收。后面的博客實例代碼也會根據實際情況來決定是否使用類型關聯(服務暴露),一切從簡了。
類型關聯
As關聯
關於類型關聯(服務暴露)的內容其實並不多,我們在進行手動關聯時,基本都是使用As進行關聯的。我們先來看看一個實例吧:
class Program { static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterType<Class1>().As<IInterface>(); var container = builder.Build(); IInterface inter = container.Resolve<IInterface>(); Console.WriteLine(inter.Id); Console.Write("Press any key to continue..."); Console.ReadKey(); } }interface IInterface { Guid Id { get; } } class Class1 : IInterface { private Guid _id; public Guid Id { get { return _id; } } }從代碼中可以看到,我們直接在類型注冊后加了一句As<…>(),然后我們在使用Resolve時,我們也是用的As的類型。
讓我們回顧一下之前的用法,之前是直接RegisterType,最后注冊的類型是Class1,Resolve時,也是直接用Class1。而現在注冊的雖然還是Class1,但是獲取時卻是用IInterface,並且最后獲取到的實例類型是Class1。這樣看有點繞,代碼層面上是這樣的,但我們也可以理解為注冊了IInterface,但是為IInterface指定了實現。這樣做的好處在於,我們如果希望更改IInterface的實現,只需要修改注冊的代碼,而不需要修改獲取處以及后續使用IInterface實例的代碼。
As還有兩種重載,一種是Type可變數組,另一種是Service可變數組。第二種方式我們一般不用,autofac底層使用,推薦篇不進行說明。
這里使用RegisterType進行注冊的,同樣,其他注冊方式也可以使用As進行類型關聯。
多關聯
一個類可能實現多個接口,如果我們希望Resolve多個接口時獲取到的都是那個類型,應該怎么做呢?最容易想到的方式就是直接再注冊As一遍就好了。嗯,這樣確實是可以的,但是大家是否記得IEnumerable和IQueryable那種鏈式編程的方式不?autofac同樣有這樣簡便的方式,如果希望多個接口或類型都與同一個類型進行關聯,我們可以直接再表達式后面繼續As:builder.RegisterType<C1>().As<I1>().As<I2>().As<I3>,如此,Resolve<I1>、Resolve<I2>、Resolve<I3>獲取到的都是C1類型實例。
自關聯AsSelf
As使用注意
不使用As時,RegisterType注冊的是什么類型,Resolve就使用什么類型進行獲取,但是在使用As后,就只能使用As的類型進行Resolve。比如前面的例子中,只能夠Resolve<IInterface>,而不能Resolve<Class1>,否則將拋出異常。
但是如果還想在Resolve<Class1>時能夠獲取到Class1而不拋出異常,應該怎么辦呢?在不知道AsSelf方法的情況下,可以在注冊時直接As<Class1>,這樣就可以了。另一種方式就是AsSelf了:
// 這兩句代碼效果相同 builder.RegisterType<Class1>().As<IInterface>().As<Class1>(); builder.RegisterType<Class1>().As<IInterface>().AsSelf();相對而言,不論什么類型,最后只需AsSelf就行了,這樣相對方便,但是具體還是要看個人習慣了。
批量關聯AsImplementedInerfaces
根據這個方法的名稱,我們大概能夠猜出它的作用了,AsImplementedInterfaces也就是直接與類型實現的接口進行類型關聯。比如有以下類型定義:
interface I1 { } interface I2 { } interface I3 { } interface I4 { } interface I5 { } interface I6 { } interface I7 { } class C1 : I1, I2, I3, I4, I5, I6, I7 { }一個類型實現了7個接口,然后我們希望Resolve這七個中任何一個接口,都能獲取到C1實例,按照我們第一節中說的多關聯,代碼可以簡寫為:
builder.RegisterType<C1>() .As<I1>().As<I2>() .As<I3>().As<I4>() .As<I5>().As<I6>() .As<I7>();雖然這個代碼相對RegisterType多次再多次As簡潔了很多,但是還是不夠簡潔,然而在有AsImplementInterfaces方法后,就能夠非常簡單:
builder.RegisterType<C1>().AsImplementedInterfaces();在程序集注冊這種方式中,AsImplementInterfaces更能顯示出威力,程序集注冊這種方式中,都是批量注冊類型的,批量注冊的這些類型,它們可能都實現了不同的接口,這樣我們沒辦法為它們一一關聯接口,但是通過AsImplementInterfaces方法,可以讓所有注冊類型自動與實現的接口進行關聯。當然,也正因為這點,在使用AsImplementInterfaces時需要注意,是否真的希望與所有接口都進行關聯。
類型關聯注意點
一個接口/類型只能與一個類型進行關聯
在前面我們看到了,可以將一個類型與多個接口進行關聯,讓多個接口Resolve結果都是同一個類型實例。需要注意的是,這個是不可逆的,也就是一個接口不能與多個類型進行關聯,因為這樣autofac不知道應該返回哪個類型實例。代碼實例:
builder.RegisterType<C1>().As<I>(); // class C1 : I builder.RegisterType<C2>().As<I>(); // class C2 : Iinterface I { } class C1 : I { } class C2 : I { }按上面的代碼,最后實際與I接口關聯的類型是C2,autofac中按注冊順序,后面注冊的會覆蓋前面注冊的。如果想要阻止這種默認行為(相同接口/類型進行多次類型關聯,后面的關聯覆蓋前面的關聯),可以在As方法調用用繼續調用PreserveExistingDefaults方法,這樣,如果之前該接口/類型已經進行關聯注冊,則此次關聯無效:
builder.RegisterType<C1>().As<I>(); // class C1 : I builder.RegisterType<C2>().As<I>().PreserveExistingDefaults(); // class C2 : I上面的代碼,通過Resolve<I>()獲取到時C1類型實例。
一個接口關聯多個類型注意
這里說的一個接口不能關聯多個類型,是針對這種常用的注冊及關聯。其實可以通過Named<>、Meta<>這種方式進行多關聯,關於此類話題后續博文將會談到。
關聯類型與注冊類型需要時繼承/實現關系或為其本身
builder.RegisterType<X>().As<Y>();當這樣進行注冊關聯時,需要X繼承或實現Y,再或者Y就是X類型,否則將在builder調用Build方法時拋出異常。
尾述
看完本篇博文,autofac最基本/常用的功能就完結了。但是現在只是最基本的功能,推薦篇並沒有結束,還有一些內容,我認為還是需要掌握的,希望大家繼續關注,謝謝!