依赖注入(C#)

一,依赖注入(Dependency Injection,简称DI)

设计模式中,尤其是结构型模式很多时候解决的就是对象间的依赖关系,变依赖关系具体为依赖具象。平时开发中如果发现客户程序依赖某个(或某类)对象,我们常常会对它们进行一次抽象,形成抽象的抽象类、接口,这样客户程序就可以摆脱所依赖的具体类型。


二,实现(C#)

示例情景

假设,现在程序需要一个获取不同时间格式的的当前时间。
我们定义一个接口ITimeProvider。

    public interface ITimeProvider
    {
        DateTime CurrentDate { get; }
    }

ITimeProvider的不同实现,提供不同的时间格式。

    public class SystemTimeProvider : ITimeProvider
    {
        public DateTime CurrentDate { get { return DateTime.Now; } }
    }

    public class UtcNowTimeProvider : ITimeProvider
    {
        public DateTime CurrentDate { get { return DateTime.UtcNow; } }
    }

需要一个装配工Assembler将所需的类型名称和实体类型一一对应。

    public class Assembler
    {
        /// <summary>
        /// 保存“类型名称/实体类型”对应关系的字典
        /// </summary>
        private static Dictionary<string, Type> dictionary = new Dictionary<string, Type>();

        // 实际的配置信息可以从外层机制获得,例如通过xml文件配置.
        static Assembler()
        {
            dictionary.Add("SystemTimeProvider", typeof(SystemTimeProvider));
            dictionary.Add("UtcNowTimeProvider", typeof(UtcNowTimeProvider));
        }

        static void RegisterType(string name,Type type)
        {
            if ((type == null) || dictionary.ContainsKey(name)) throw new NullReferenceException();
            dictionary.Add(name, type);
        }

        static void Remove(string name)
        {
            if (string.IsNullOrEmpty(name)) throw new NullReferenceException();
            dictionary.Remove(name);
        }

        /// <summary>
        /// 根据程序需要的类型名称选择相应的实体类型,并返回类型实例
        /// </summary>
        public ITimeProvider Create(string type)
        {
            if ((type == null) || !dictionary.ContainsKey(type)) throw new NullReferenceException();
            Type targetType = dictionary[type];
            return (ITimeProvider)Activator.CreateInstance(targetType);
        }
}

此时出现一个问题,程序如何获取所需的时间格式的实现类呢?通过注入的方式。
注入的方式有以下几种。

构造函数(Constructor)注入

构造函数注入,顾名思义,就是在构造函数的时候,通过Assembler或其它机制把抽象类型作为返回给所需的程序。

    /// <summary>
    /// 在构造函数中注入
    /// </summary>
    class Client
    {
        private ITimeProvider timeProvider;

        public Client(ITimeProvider timeProvider)
        {
            this.timeProvider = timeProvider;
        }
    }

    class ConstructorInjectionTest
    {
        public static void Test()
        {
            ITimeProvider timeProvider = (new Assembler()).Create("SystemTimeProvider");
            Client client = new Client(timeProvider);   // 在构造函数中注入
        }
    }

Setter注入

Setter注入是通过属性复制的方法解决的,由于Java等很多语言中没有真正的属性,所以Setter注入一般通过一个set()方法实现,C#语言由于本身就有可写属性,所以实现起来更简洁,更像Setter。想比较Constructor方式而言,Setter给了客户类型后续修改的机会,它比较适合客户类型实例存活时间较长,但Assembler修改抽象类型指定的具体类型相对较快的情景;不过也可以由客户程序根据需要动态设置所需的类型。

    /// <summary>
    /// 通过Setter实现注入
    /// </summary>
    class Client
    {
        private ITimeProvider timeProvider;

        public ITimeProvider TimeProvider
        {
            get { return this.timeProvider; }   // getter本身和Setter方式实现注入没有关系
            set { this.timeProvider = value; }
        }
    }

    class SetterInjectionTest
    {
        public static void Test()
        {
            ITimeProvider timeProvider = (new Assembler()).Create("SystemTimeProvider");
            Client client = new Client();
            client.TimeProvider = timeProvider; // 通过Setter实现注入
        }
    }

接口注入

接口注入是将包括抽象类型注入的入口以方法的形式定义在一个借口里,如果客户类型需要实现这个注入过程,则实现这个接口,客户类型自己考虑如何把抽象类型引入内部。实际上接口注入有很强的侵入性,除了要求客户类型增加需要的Setter或Constructor注入的代码外,还显示地定义了一个新的接口并要求客户类型实现它。除非还有更外层容器使用的要求,或者有完善的配置系统,可以通过反射动态实现接口注入方式注入,否则并不建议采用接口注入方式。

    /// <summary>
    /// 定义需要注入ITimeProvider的类型
    /// </summary>
    interface IObjectWithTimeProvider
    {
        ITimeProvider TimeProvider { get; set; }
    }

    /// <summary>
    /// 通过接口方式注入
    /// </summary>
    class Client : IObjectWithTimeProvider
    {
        private ITimeProvider timeProvider;

        /// <summary>
        /// IObjectWithTimeProvider Members
        /// </summary>
        public ITimeProvider TimeProvider
        {
            get { return this.timeProvider; }
            set { this.timeProvider = value; }
        }
    }

    class InterfacerInjectionTest
    {
        public static void Test()
        {
            ITimeProvider timeProvider = (new Assembler()).Create("SystemTimeProvider");
            IObjectWithTimeProvider objectWithTimeProvider = new Client();
            objectWithTimeProvider.TimeProvider = timeProvider; // 通过接口方式注入
        }
    }

基于Attribute实现注入

可以通过Attribute将附加的内容注入到对象上。

    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    sealed class DecoratorAttribute : Attribute
    {

        public readonly object Injector;
        private Type type;

        public DecoratorAttribute(Type type)
        {
            if (type == null) throw new ArgumentNullException("type");
            this.type = type;
            Injector = (new Assembler()).Create("SystemTimeProvider");
        }

        public Type Type { get { return this.type; } }
    }

    /// <summary>
    /// 用户帮助客户类型和客户程序获取其Attribute定义中需要的抽象类型实例
    /// </summary>
    static class AttributeHelper
    {
        public static T Injector<T>(object target)
            where T : class
        {
            if (target == null) throw new ArgumentNullException("target");
            Type targetType = target.GetType();
            object[] attributes = targetType.GetCustomAttributes(typeof(DecoratorAttribute), false);
            if ((attributes == null) || (attributes.Length <= 0)) return null;
            foreach (DecoratorAttribute attribute in (DecoratorAttribute[])attributes)
                if (attribute.Type == typeof(T))
                    return (T)attribute.Injector;
            return null;
        }
    }

    [Decorator(typeof(ITimeProvider))]
    class Client
    {
        public string GetTime()
        {
            // 与其他方式注入不同的是,这里使用的ITimeProvider来自自己的Attribute
            ITimeProvider provider = AttributeHelper.Injector<ITimeProvider>(this);
            return provider.CurrentDate.ToString();
        }
    }

    class AttributerInjectionTest
    {
        public static void Test()
        {
            Client client = new Client();
            string time = client.GetTime();
        }
    }

示例代码https://github.com/BinGithub2015/DependencyInjectionDemo


三,小结

依赖注入各种实现方式对比:
构造函数(Constructor)注入方式:它的注入是一次性的,当客户类型构造的时候就确定了。它很适合那种生命周期不长的对象,比如在其存续期间不需要重启适配的对象。另外,相对Setter方式而言,在实现上Constructor可以节省很多代码。
Setter方式:一个很灵活的实现方式,对于生命周期较长的客户对象而言,可以在运行过程中随时适配。
接口方式:作为注入方式具有侵入性,很多成都上它适合需要同时约束一批客户类型的情况。
属性方式:本身具有范围较小的固定内容侵入性(一个Attribute),它适合需要同时约束一批客户类型的情景。它本身实现相对复杂一些,但客户类型使用的时候非常方便,打标签即可。


四,参考资料

《设计模式:基于C#的工程化实现及扩展》

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C#中,依赖注入是一种设计模式,用于将一个对象的依赖关系从它的客户端解耦。通过依赖注入,创建对象时,它所依赖的其他对象会被注入到它的构造函数、属性或方法中,从而实现对象之间的松耦合。 在C#中,可以使用构造方法注入和属性注入两种方式来实现依赖注入。 构造方法注入是在对象的构造方法中将依赖对象作为参数传入,例如: ```csharp internal class ClientClass { private IServiceClass _serviceImpl; public ClientClass(IServiceClass serviceImpl) { this._serviceImpl = serviceImpl; } // ... } ``` 属性注入是通过对象的属性来注入依赖对象,例如: ```csharp internal class ClientClass { public IServiceClass ServiceImpl { get; set; } // ... } ``` 需要注意的是,构造方法注入只能在实例化客户类时注入一次,而属性注入可以在程序运行期间改变客户类对象内的服务类实例。 为了实现依赖注入,通常需要使用一个依赖注入容器(例如:Microsoft.Extensions.DependencyInjection),通过容器来管理依赖对象的创建和注入。 除了依赖注入容器,还可以使用配置文件来管理依赖对象的创建和注入。可以使用ConfigurationBuilder类来导入配置文件,并使用IOptions<T>来获取配置文件中的内容。 总结起来,C#中的依赖注入是通过构造方法注入或属性注入来创建对象并将依赖对象注入其中。使用依赖注入容器可以更方便地管理依赖对象的创建和注入,而配置文件可以用于配置和管理依赖对象的属性值。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [C#依赖注入](https://blog.csdn.net/qq_18145031/article/details/83181937)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [c#依赖注入](https://blog.csdn.net/wanxiweilai/article/details/127878160)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值