前言
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最基本/常用的功能就完结了。但是现在只是最基本的功能,推荐篇并没有结束,还有一些内容,我认为还是需要掌握的,希望大家继续关注,谢谢!