My.Ioc 代码示例——使用默认构造参数和覆盖参数

在 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(30),
                Parameter.Positional("China"),
                Parameter.Positional<ParameterClass5>());

            container.Register<NamedTarget>()
                .WithConstructor(Parameter.Named("age", 90));

            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(99),
                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(12));
            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", 68));
            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(30),
    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 方法:

 1 static void Register(IObjectContainer container)
 2 {
 3     container.Register<ParameterClass1>();
 4     container.Register<ParameterClass2>();
 5     container.Register<ParameterClass3>();
 6     container.Register<ParameterClass4>();
 7     container.Register<ParameterClass5>();
 8
 9     container.Register<PositionalTarget>()
10         .WithConstructor(
11         Parameter.Auto,
12         Parameter.Positional(30),
13         Parameter.Positional("China"),
14         Parameter.Positional<ParameterClass5>());
15
16     container.Register<NamedTarget>()
17         .WithConstructor(Parameter.Named("age", 90));
18
19     container.CommitRegistrations();
20 }

在这个方法中,我们首先注册了 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 框架源码可在此处获取。

时间: 2024-10-11 03:31:30

My.Ioc 代码示例——使用默认构造参数和覆盖参数的相关文章

My.Ioc 代码示例——使用 Observer 机制捕获注册项 (Registration) 状态的变化

在 My.Ioc 中,要想在服务注销/注册时获得通知,可以通过订阅 ObjectBuilderRegistered 和 ObjectBuilderUnregistering 这两个事件来实现.但是,使用这两个事件也有一些不足.首先,它们只能针对当前注册/注销的服务发出通知,而对于依赖当前服务的上层服务的激活/停用事件(由于当前服务的注册/注销而引起的),它们则无能为力:其次,这两者都是针对所有注册项的广播事件.也就是说,只要发生注册/注销,无论注册/注销的是什么服务,容器都会向所有订阅了这两个事

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

装饰器模式体现了一种“组合优于继承”的思想.当我们要动态为对象增加新功能时,装饰器模式往往是我们的好帮手. 很多后期出现的 Ioc 容器都为装饰器模式提供了支持,比如说 Autofac.在 My.Ioc 中,默认不提供装饰器支持,但我们可以自己进行扩展,以提供此项功能. using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using My.Ioc; u

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

在使用 Ioc 框架时,一般我们建议集中在一个称为 Composition Root(其含义请参见下面的小注)的位置来注册 (Register) 和解析 (Resolve) 服务.该做法的目的在于通过限制 Ioc 的使用场合,尽量减少应用程序本身对于 Ioc 框架的依赖. 这种模式固然能够很好地解耦应用程序和 Ioc 框架,使我们能够在需要的时候方便地更换 Ioc 框架,但它同时也带来了一个问题:难道我们一定要在程序启动时注册所有服务吗?有些服务并不一定会马上用到,有一些服务甚至可能不会用到.在

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

很多 Ioc 框架在创建对象的过程中,都会采取某种方式来缓存/复用/释放已构建的对象.在 My.Ioc 中,这个目的是通过 Lifetime/ILifetimeScope 来实现的.其中,Lifetime 实现了缓存/复用对象的功能,ILifetimeScope 则实现了复用/释放对象的功能. My.Ioc 默认提供了三种 Lifetime:ContainerLifetime.TransientLifetime 和 ScopeLifetime.这里简单解释一下它们的含义:ContainerLif

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

本文的目的在于通过一些示例,向用户说明 My.Ioc 支持哪些类型的依赖关系.也就是说,如何设计对象不会导致循环依赖. 在 Ioc 世界中,循环依赖是一个顽敌.这不仅因为它会导致 Ioc 容器抛出异常,而且还因为它是不可预知的,尽管通过仔细的配置是可以尽量避免这个问题的. 当用户在 Ioc 容器中注册对象时,他们事先并不知道该对象与其他对象之间的依赖关系,因为依赖关系是由 Ioc 容器管理的.这种依赖关系要等到用户首次调用 container.Resolve(contractType) 时才能确

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

当您需要从 Ioc 容器中注销/删除一个注册项的时候,您会怎么做呢? 有人曾经在 stackoverflow 上提问“如何从 Unity 中注销一个注册项”,对于这个问题,有人的回答是“有趣.你为什么要这样做?”,也有人提出了一些变通的解决办法,例如通过自定义 LifetimeManager 来实现等等.这些其实都不是根本的解决办法.因为服务的注册/注销本身是一个容器级过程,当中一定会涉及到一些中间对象的创建/清理 (Dispose) 以及不同对象之间的协调,本应由容器来提供支持才对. 遗憾的是

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

本文的目的在于通过创建一棵插件树来演示条件绑定和元数据的用法. 说“插件树”也许是不大妥当的,因为在一般观念中,谈到插件树,我们很容易会想到 Winform/Wpf 中的菜单.举例来说,如果要在 Winform 中创建一个菜单,我们使用类似如下代码: // Create File menu var newMenu = new ToolStripMenuItem(); var localProjectMenu = new ToolStripMenuItem(); var remoteProject

NumPy常用函数(一)——构造数组函数及代码示例

NumPy是Python的一个科学计算的基本模块.它是一个Python库,提供了一个多维数组对象,各种衍生对象(如屏蔽数组和矩阵),以及用于数组,数学,逻辑,形状操纵,排序,选择,I/O等快速操作的各种例程 离散傅里叶变换,基本线性代数,基本统计运算,随机模拟等等. 本文主要列出构造数组常用的函数或者成为子模块 一.0-1数组 empty(shape [,dtype,order])                      返回给定形状和类型的新数组,而不初始化条目. empty_like(a

Java中普通代码块,构造代码块,静态代码块的代码示例及区分

//执行顺序:(优先级从高到低.)静态代码块>mian方法>构造代码块>构造方法. 其中静态代码块只执行一次.构造代码块在每次创建对象是都会执行. 1 普通代码块 <span style="font-size:12px;">//普通代码块:在方法或语句中出现的{}就称为普通代码块.普通代码块和一般的语句执行顺序由他们在代码中出现的次序决定--"先出现先执行" public class CodeBlock01{ public static