在 Ioc 世界中,有些框架(例如 Autofac/NInject/Unity)支持传递默认参数,有些框架(例如 SimpleInjector/LightInjector 等)则不支持。作为 My.Ioc 来说,我们支持默认参数。

当我们在 My.Ioc 中注册对象时,有些对象类型 (System.Type) 要求我们必须提供默认参数,而有些则不是必要的。在 My.Ioc 中,默认参数有两个作用:1. 容器根据默认参数来选择用于构建对象的构造函数。而一旦选定构造函数之后,今后容器便会一直使用该构造函数来构造对象实例;2. 如果我们在向容器请求对象实例(即调用任何一个 container.Resolve 重载方法)时没有传入覆盖参数(即未调用任何包含 overridenParameters 参数的 container.Resolve 重载方法),容器便会使用这些默认参数来构造对象实例。下面我们通过一个示例来加以说明:

using System;
using System.Diagnostics;
using My.Ioc; namespace ConstructorSelectAndParameterOverride
{
#region Test Types public class ParameterClass1
{
}
public class ParameterClass2
{
}
public class ParameterClass3
{
}
public class ParameterClass4
{
}
public class ParameterClass5
{
}
public class PositionalTarget
{
private ParameterClass1 _c1;
private ParameterClass2 _c2;
private ParameterClass3 _c3;
private ParameterClass4 _c4;
private ParameterClass5 _c5;
private int _age;
private string _name; public PositionalTarget(ParameterClass1 c1, int age, string name, ParameterClass2 c2, ParameterClass3 c3, ParameterClass4 c4)
{
_c1 = c1;
_c2 = c2;
_c3 = c3;
_c4 = c4;
_name = name;
_age = age;
} public PositionalTarget(ParameterClass1 c1, int age, string name, ParameterClass5 c5, ParameterClass3 c3, ParameterClass4 c4)
{
_c1 = c1;
_c5 = c5;
_c3 = c3;
_c4 = c4;
_name = name;
_age = age;
} public ParameterClass5 ParameterClass5
{
get { return _c5; }
} public ParameterClass2 ParameterClass2
{
get { return _c2; }
} public string Name
{
get { return _name; }
} public int Age
{
get { return _age; }
}
}
public class NamedTarget
{
private ParameterClass1 _c1;
private ParameterClass2 _c2;
private ParameterClass3 _c3;
private ParameterClass4 _c4;
private ParameterClass5 _c5;
private int _age;
private string _name; public NamedTarget(ParameterClass1 c1, int age, ParameterClass2 c2, ParameterClass3 c3, string name, ParameterClass4 c4)
{
_c1 = c1;
_c2 = c2;
_c3 = c3;
_c4 = c4;
_name = name;
_age = age;
} public NamedTarget(ParameterClass1 c1, int age, ParameterClass5 c5, ParameterClass3 c3, ParameterClass4 c4)
{
_c1 = c1;
_c5 = c5;
_c3 = c3;
_c4 = c4;
_age = age;
} public ParameterClass5 ParameterClass5
{
get { return _c5; }
} public ParameterClass2 ParameterClass2
{
get { return _c2; }
} public string Name
{
get { return _name; }
} public int Age
{
get { return _age; }
}
} #endregion class Program
{
static void Main(string[] args)
{
IObjectContainer container = new ObjectContainer(false);
Register(container); ResolvePositionalTargetWithoutOverridingParameters(container);
ResolvePositionalTargetAndOverrideWithPositionalParameters(container);
ResolvePositionalTargetAndOverrideWithNamedParameters(container); ResolveNamedTargetWithoutOverridingParameters(container);
ResolveNamedTargetAndOverrideWithPositionalParameters(container);
ResolveNamedTargetAndOverrideWithNamedParameters(container); Console.ReadLine();
} static void Register(IObjectContainer container)
{
container.Register<ParameterClass1>();
container.Register<ParameterClass2>();
container.Register<ParameterClass3>();
container.Register<ParameterClass4>();
container.Register<ParameterClass5>(); container.Register<PositionalTarget>()
.WithConstructor(
Parameter.Auto,
Parameter.Positional(),
Parameter.Positional("China"),
Parameter.Positional<ParameterClass5>()); container.Register<NamedTarget>()
.WithConstructor(Parameter.Named("age", )); container.CommitRegistrations();
} static void ResolvePositionalTargetWithoutOverridingParameters(IObjectContainer container)
{
Console.WriteLine("Resolve PositionalTarget without overriding default parameters: "); var positional = container.Resolve<PositionalTarget>();
Debug.Assert(positional.ParameterClass5 != null);
Console.WriteLine(positional.Name);
Console.WriteLine(positional.Age);
} static void ResolvePositionalTargetAndOverrideWithPositionalParameters(IObjectContainer container)
{
Console.WriteLine("Resolve PositionalTarget and override default parameters using positional parameters: "); var positional = container.Resolve<PositionalTarget>(
Parameter.Auto,
Parameter.Positional(),
Parameter.Positional("ZhongHua"));
Debug.Assert(positional.ParameterClass5 != null);
Console.WriteLine(positional.Name);
Console.WriteLine(positional.Age);
} static void ResolvePositionalTargetAndOverrideWithNamedParameters(IObjectContainer container)
{
Console.WriteLine("Resolve PositionalTarget and override default parameters using named parameters: "); var positional = container.Resolve<PositionalTarget>(
Parameter.Named("name", "HuaXia"));
Debug.Assert(positional.ParameterClass5 != null);
Console.WriteLine(positional.Name);
Console.WriteLine(positional.Age);
} static void ResolveNamedTargetWithoutOverridingParameters(IObjectContainer container)
{
Console.WriteLine("Resolve NamedTarget without overriding default parameters: "); var named = container.Resolve<NamedTarget>();
Debug.Assert(named.ParameterClass5 != null);
Debug.Assert(named.Name == null);
Console.WriteLine(named.Age);
} static void ResolveNamedTargetAndOverrideWithPositionalParameters(IObjectContainer container)
{
Console.WriteLine("Resolve NamedTarget and override default parameters using positional parameters: "); var named = container.Resolve<NamedTarget>(
Parameter.Auto,
Parameter.Positional());
Debug.Assert(named.ParameterClass5 != null);
Debug.Assert(named.Name == null);
Console.WriteLine(named.Age);
} static void ResolveNamedTargetAndOverrideWithNamedParameters(IObjectContainer container)
{
Console.WriteLine("Resolve NamedTarget and override default parameters using named parameters: "); var named = container.Resolve<NamedTarget>(
Parameter.Named("age", ));
Debug.Assert(named.ParameterClass5 != null);
Debug.Assert(named.Name == null);
Console.WriteLine(named.Age);
}
}
}

在上面这个示例中,我们看到有两个类 PositionalTarget 和 NamedTarget,这两个是我们打算向容器请求的目标类。之所以有两个,是因为我们要分别演示两种传递默认参数的方法,即命名参数 (Named Parameter) 和定位参数 (Positional Parameter) 的用法。

在 My.Ioc 中,我们把参数分为两个大类:一种是可以自动装配 (Autowirable) 的参数,一种是不可自动装配 (NonAutowirable) 的参数。不可自动装配的参数,顾名思义,容器没有办法为这类参数自动构建一个参数值。这类参数包括所有值类型,以及 string 型和 Type 型(请参见 My.Ioc.Helpers.TypeExtensions 的 IsNotAutowirable 和 IsAutowirable 两个扩展方法)。可以自动装配的参数则是除了不可自动装配的参数之外的所有其他参数类型,这种类型的参数是容器可以自动装配的(废话),它们要么是已经注册的类型,要么是符合某种约定的、容器可以自动注册/解析的类型(参见My.Ioc 代码示例——实现自动注册/解析)。

对于可以自动装配的参数来说,是否提供默认值是可选的。而对于不可自动装配的参数来说,由于其是不可自动装配的(又是废话),因此我们必须为其提供一个默认值。

如果要提供默认参数值,则有两个选择:或是以定位参数方式提供默认值,或是以命名参数方式提供默认值,二者不可混用。也就是说,在提供默认参数值时,不能同时使用命名参数和定位参数,只能任意选用其中一种。譬如,下面这种用法就是错误的(在 IDE 中也会提示出错,而无法通过编译):

container.Register<PositionalTarget>()
.WithConstructor(
Parameter.Auto,
Parameter.Positional(),
Parameter.Named("name", "China"),
Parameter.Positional<ParameterClass5>());

在选择(或者说匹配)构造函数时,无论使用定位参数还是命名参数,有一个原则是二者都必须共同遵循的,那就是 [所有“不可自动装配的参数”都必须显式指定一个默认值]。但在用法方面,二者略有一些不同。

使用定位参数时,容器将根据参数位置来匹配参数。这里有一点要特别说明一下。在一些构造函数中,有可能会出现“不可自动装配的参数”位于“可以自动装配的参数”后面的情况(本文的示例代码中给出的就是这种情况)。在这种情况下,由于是按位置匹配参数,因此对于排在“不可自动装配的参数”之前的“可以自动装配的参数”,也需要显式提供一个默认值。但此时我们既可以指定提供一个该类型的对象实例作为参数值,也可以使用“Parameter.Auto”。后者是 My.Ioc 容器中提供的一种占位参数,表示此构造参数的参数值将由容器自行决定(即由容器自动装配)。

使用命名参数时,参数位置不重要,容器将会根据参数名称来匹配参数。一般说来,只要参数名称正确,而且为所有“不可自动装配的参数”都提供了默认值,容器便能成功匹配构造函数。

有了上面这些知识作为背景,接下来我们来看一下 PositionalTarget 这个类的构造函数。我们看到在 PositionalTarget 这个类中,有两个构造函数,其签名分别是:

public PositionalTarget(ParameterClass1 c1, int age, string name, ParameterClass2 c2, ParameterClass3 c3, ParameterClass4 c4)
public PositionalTarget(ParameterClass1 c1, int age, string name, ParameterClass5 c5, ParameterClass3 c3, ParameterClass4 c4)

在这两个构造函数中,都包含了 int 类型和 string 类型的构造参数,因此,我们知道要想构造这个类的对象,无论选择哪个构造函数都是需要提供默认参数的。

在示例代码中,对象注册是在 Register 方法中完成的。因此,接下来我们就来看一下这个 Register 方法:

 static void Register(IObjectContainer container)
{
container.Register<ParameterClass1>();
container.Register<ParameterClass2>();
container.Register<ParameterClass3>();
container.Register<ParameterClass4>();
container.Register<ParameterClass5>(); container.Register<PositionalTarget>()
.WithConstructor(
Parameter.Auto,
Parameter.Positional(),
Parameter.Positional("China"),
Parameter.Positional<ParameterClass5>()); container.Register<NamedTarget>()
.WithConstructor(Parameter.Named("age", )); container.CommitRegistrations();
}

在这个方法中,我们首先注册了 ParameterClass1、ParameterClass2、ParameterClass3、ParameterClass4 和 ParameterClass5 等类,这几个类型将要用作 PositionalTarget 和 NamedTarget 的构造参数,它们也是所谓的“可以自动装配的参数”。由于它们已经注册到容器中,因此容器在创建 PositionalTarget 和 NamedTarget 的实例时,便可以自动创建这几个类的实例以满足构造函数需要(也即自动装配的意思)。

接下来,在第 9 行到第 14 行中,我们采用定位参数的方式指定了用于构造 PositionalTarget 对象的默认参数。我们看到第一个默认参数是 Parameter.Auto。这是由于该参数是 ParameterClass1 类型(属于“可以自动装配的参数”),但它后面的构造参数是 int 类型(属于“不可自动装配的参数”),所以我们这里需要为它提供一个默认参数值。

后面两个参数 Parameter.Positional(30) 和 Parameter.Positional("China") 的含义都比较简单,这里略过不谈。

我们看到最后还提供了一个默认参数 Parameter.Positional<ParameterClass5>()。这里之所以要提供这个参数,是因为 PositionalTarget 这个类的构造函数中,前面几个构造参数的参数类型都完全相同,因此没有办法区分到底匹配哪个构造函数。只有到了第四个构造参数时,其参数类型才显示出有所不同(一个是 ParameterClass5 类型,一个是 ParameterClass2 类型),因此我们这里需要提供这个默认参数才能加以区分。当我们提供了这个默认参数之后,要选用哪个构造函数的问题也就一目了然了。

在 Register 这个方法的末后,可以看到我们注册了 NamedTarget 这个类型。NamedTarget 这个类型也有两个构造函数,分别是:

public NamedTarget(ParameterClass1 c1, int age, ParameterClass2 c2, ParameterClass3 c3, string name, ParameterClass4 c4)
public NamedTarget(ParameterClass1 c1, int age, ParameterClass5 c5, ParameterClass3 c3, ParameterClass4 c4)

在上面注册 NamedTarget 的代码中,我们看到只提供了一个命名参数 Parameter.Named("age", 90)。根据 [所有“不可自动装配的参数”都要显式指定一个默认值] 的原则,我们也可以很容易明白作者想要选用的是下面一个构造函数。

上面,我们向大家介绍了在什么情况下需要提供默认参数以及如何提供默认参数。接下来,我们将向各位简单介绍一下如何覆盖在注册时提供的默认参数。

在一般的编程实践中,如果我们要创建一个对象,我们可以给这个对象的构造函数传递不同的参数值以创建不同的对象实例。My.Ioc 中的覆盖参数 (Overridden Parameters) 的意义也即在此。通过使用覆盖参数,我们也可以要求容器为我们提供不同的对象实例。

在使用覆盖参数时,由于此时对象的默认构造函数已经由容器选定,而且所有不可自动装配的参数也都有了默认值,因此我们不需要为所有“不可自动装配的参数”指定一个覆盖参数值。如果我们想要覆盖某个默认参数,我们便为其提供一个覆盖参数值;如果我们不需要覆盖某个默认参数,只需略过该参数即可。但是,我们传递的覆盖参数仍然要符合一定的原则。

譬如,如果我们以定位方式传递覆盖参数,仍然需要符合 [对于排在“不可自动装配的参数”之前的“可以自动装配的参数”,需要显式提供一个覆盖参数值] 这个原则。也就是说,即使我们要覆盖的目标参数前面的参数是可以自动装配的,我们也要为其提供一个覆盖参数值。一般情况下,提供 Parameter.Auto 即可。此外,如果在要覆盖的目标参数的前面还有其他“不可自动装配的参数”,而我们并不想覆盖它们,也可以使用 Parameter.Auto。

使用命名参数方式提供覆盖参数时,只需按名称指定要覆盖的参数,并为其提供一个参数值即可。用法比较简单,此处不再多言。

本文示例代码以及 My.Ioc 框架源码可在此处获取。

My.Ioc 代码示例——如何使用默认构造参数,以及如何覆盖默认构造参数的更多相关文章

  1. My.Ioc 代码示例——实现自动注册/解析

    在很多 Ioc 容器中,当使用者向容器请求实现了某个契约类型 (Contract Type) 的服务时 (调用类似如下方法 container.Resolve(Type contractType)), ...

  2. My.Ioc 代码示例——使用观察者机制捕获注册项状态的变化

    在 My.Ioc 中,要想在服务注销/注册时获得通知,可以通过订阅 ObjectBuilderRegistered 和 ObjectBuilderUnregistering 这两个事件来实现.但是,使 ...

  3. My.Ioc 代码示例——谈一谈如何实现装饰器模式,兼谈如何扩展 My.Ioc

    装饰器模式体现了一种“组合优于继承”的思想.当我们要动态为对象增加新功能时,装饰器模式往往是我们的好帮手. 很多后期出现的 Ioc 容器都为装饰器模式提供了支持,比如说 Autofac.在 My.Io ...

  4. My.Ioc 代码示例——Lifetime 和 ILifetimeScope

    很多 Ioc 框架在创建对象的过程中,都会采取某种方式来缓存/复用/释放已构建的对象.在 My.Ioc 中,这个目的是通过 Lifetime/ILifetimeScope 来实现的.其中,Lifeti ...

  5. My.Ioc 代码示例——避免循环依赖

    本文的目的在于通过一些示例,向大家说明 My.Ioc 支持哪些类型的依赖关系.也就是说,如何设计对象不会导致循环依赖. 在 Ioc 世界中,循环依赖是一个顽敌.这不仅因为它会导致 Ioc 容器抛出异常 ...

  6. My.Ioc 代码示例——利用 ObjectBuilderRequested 事件实现延迟注册

    在使用 Ioc 框架时,一般我们建议集中在一个称为 Composition Root(其含义请参见下面的小注)的位置来注册 (Register) 和解析 (Resolve) 服务.这种做法的目的在于限 ...

  7. My.Ioc 代码示例——属性和方法注入

    在 My.Ioc 中,我们可以指定让容器在构建好对象实例之后,自动为我们调用对象的公共方法或是为对象的公共属性赋值.在解析对象实例时,容器将根据我们在注册对象时指定的方法调用或属性赋值的先后顺序,调用 ...

  8. My.Ioc 代码示例——使用条件绑定和元数据(可选)构建插件树

    本文旨在通过创建一棵插件树来演示条件绑定和元数据的用法. 说“插件树”也许不大妥当,因为在一般观念中,谈到插件树,我们很容易会想到 Winform/Wpf 中的菜单.举例来说,如果要在 Winform ...

  9. My.Ioc 代码示例——注册项的注销和更新

    当您需要从 Ioc 容器中注销/删除一个注册项的时候,您会怎么做呢? 有人曾经在 stackoverflow 上提问“如何从 Unity 中注销一个注册项”.对于这个问题,有人的回答是“有趣.你为什么 ...

随机推荐

  1. 关于float与double

    //float与double的范围和精度 1. 范围 float和double的范围是由指数的位数来决定的. // float的指数位有8位,而double的指数位有11位,分布如下:// float ...

  2. lpad rpad

    Lpad()函数的用法:lpad函数将左边的字符串填充一些特定的字符其语法格式如下:         lpad(string,n,[pad_string])     string:可是字符或者参数   ...

  3. BZOJ 1015 星球大战

    Description 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治者整个星系.某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通过 ...

  4. 从相对路径说开来(从C++到Qt)

    从相对路径说开来(从C++到Qt) 转载自:http://blog.csdn.net/dbzhang800/article/details/6363165 在Qt论坛经常看到网友抱怨: QPixmap ...

  5. Hibernate 配置详解(2)

    6) hibernate.session_factory_name: 配置一个JNDI名称,通过Configuration对象创建的SessionFactory会绑定到JNDI下该名称中.一般名字格式 ...

  6. ikely()与unlikely() 都等同于if, 此处只是做编译优化

    ikely()与unlikely()在2.6内核中,随处可见,那为什么要用它们?它们之间有什么区别呢? 首先明确: if (likely(value))等价于if (value)if (likely( ...

  7. 字符串(KMP):BZOJ 3670 [Noi2014]动物园

    3670: [Noi2014]动物园 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1521  Solved: 813[Submit][Status] ...

  8. dll文件已经引用,但using找不到命名空间

    一:问题截图 二:解决办法 1.没看到lz的代码中有Vancl.Server的dll. 2.确实有编译不过的问题,是Vancl.WindowsServices这个工程的target framework ...

  9. 【索引】Volume 0. Getting Started

    AOAPC I: Beginning Algorithm Contests (Rujia Liu) Volume 0. Getting Started 10055 - Hashmat the Brav ...

  10. linux —— 学习笔记(汇总)

    笔记目录:一.系统知识 和 基本概念                    二.常用操作                   三.系统管理(内存.设备.服务等管理)                   ...