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(); } }
这种方式在类型已经注册的情况下使用时没问题的,能够获取到注册类型的实例对象,但是如果类型没有经过注册,直接Resolve解析获取,便会抛出异常。
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(); } }
我们可以使用ResolveOptional进行解析获取,当类型没有经过注册时,ResolveOptional方法将会返回null作为结果。
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时,将会以哪个构造方法为准呢?答案是——尽可能最多参数,下面我们以实例来分析:
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 的)。
按照字面上里说明”最多参数“,那么理应执行的是最后一个构造方法或倒数第二个构造方法,但是为什么却是第三个?。
先抛开为什么执行的第三个构造方法,我们还是会有疑问”如果执行的是第三个构造方法,那么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时传入了一个NamedParameter,NamedParameter表示按名字匹配参数,上面的代码表示,为参数名为guid的构造参数传入了Guid.NewGuid值。这段代码最后执行的是第四个构造方法。因为第四个构造方法是能够匹配的最多参数的构造方法。
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; } }