【问题标题】:Creating a small IoC Container in C#在 C# 中创建一个小型 IoC 容器
【发布时间】:2015-12-10 15:40:19
【问题描述】:

我决定用 C# 为 MonoGame 项目创建一个非常小的 IoC 容器。我决定自己创建一个的原因是为了提高性能并使用更少的我无法控制的库。由于 IoC 是如此简单,我认为图书馆不应该处理它。

我开始了一个幼稚的实现:

var container = new Container();
container.Register("service-alias",
    container => new ServiceClass(container.Resolve("other.dep"));

container.Resolve("service-alias").MethodOnServiceClass()

但我不知道如何在 C# 的类型系统中执行此操作。 Dictionary <string, Func<Container>>?如何键入 resolve 方法的返回值?

【问题讨论】:

  • 使用泛型。或者使用任何免费提供的 IoC 容器,不要重新发明轮子。
  • 作为学习练习,一定要编写自己的 IoC 容器。我不建议您自己编写用于生产用途。我参与了一个他们这样做的项目 - 这根本不值得,并且慢慢地(从人力的角度来看,代价高昂)被 Autofac 取代。
  • 感谢您的支持,克里斯!关于泛型,我不明白他们如何在这里帮助我。 Container 应该是一个泛型类吗?但是,我将拥有许多容器,而不是单例。

标签: c# inversion-of-control ioc-container


【解决方案1】:

这是example implementation with 21 lines of code。但是请不要试图通过实现一些保存注册的字典来简化开发(除了出于教育目的这样做)。手动滚动您自己的 DI 库有很多缺点。正如here 所解释的那样,您最好通过应用Pure DI(这意味着:没有DI 库的DI)并稍后从纯DI 切换到DI 库,以防万一 - 并且仅在万一 - 你的Composition Root 变成没有就很难维护。

【讨论】:

  • 我刚刚注意到这种方法的一个问题:如果我需要注册同一个类的两个实例,但配置不同怎么办?比如,FakeMailer 和 TrueMailer,Mailer 类的相同实例,但 SMTP 配置不同?
  • @vinyllinux - 通常,您希望在测试项目和生产项目中这样做,在这种情况下,您将处理 2 个不同的 DI 容器。但是,如果您想在运行时在实例之间切换,您应该在应用程序内部创建 Abstract FactoryStrategy(或其他设计模式),并完全不考虑 DI 容器。
  • 我几乎从不建议抽象工厂,就像@nightowl888 建议的那样。我通常会创建一个代理或复合IMailer 实现,它将根据所需的运行时条件分派到正确的实现。但前提是当然需要运行时切换。正如 Nightowl 所说,您的测试使用不同的容器实例(在集成测试的情况下)或根本不使用容器(使用单元测试)。
  • 好的,但想象一下:一个邮件程序发送给 A,一个邮件程序发送给 B。相同的接口。但是依赖项 C 需要将邮件程序配置为 A,而依赖项 D 需要将邮件程序配置为 B。你明白我的意思吗?我以前可能表达得很糟糕。
  • 答案保持不变。 1. 不要注入运行时数据。 2. 抽象工厂可能有用,但通常有更好的设计选择。
【解决方案2】:

我在这里真的推荐一种务实的方法。

1) 为“您梦寐以求的 IoC 容器”设计一个抽象,仅包含您需要的最低限度。像这样的:

public interface IContainer 
{
    void RegisterType<TSource, TDestination>();
    void RegisterType<TSource>(Func<TSource, TDestination> generator);
    T Resolve<T>(); 
}

2) 创建抽象的实现,将所有功能简单地委托给现有组件。我推荐Autofac,但海里有很多鱼。

3) 使用您的“包装器 IoC”开发您的应用程序。

4) 如果在某些时候您发现外部 IoC 组件存在性能问题(或任何其他类型的问题),请编写另一个使用另一个外部组件(您自己的)的 IoC 抽象实现代码,或两者的组合。即使您的应用程序处于高级状态,您也只需更改用于实例化 IoC 包装器的一小段代码(可能只是一行代码)。

这种方法的优点:

  1. 您使用成熟且经过充分测试的 IoC 容器(如果您明智地选择),同时将其复杂性隐藏在一个小接口后面。这有助于提高代码的可读性。
  2. 您不会陷入过早的优化陷阱。
  3. 您可以从一个 IoC 容器完全切换到另一个 IoC 容器,而对现有代码的影响很小(如果您的抽象设计得很好)。这消除了(或至少最小化)“使用我无法控制的库”的担忧。

当然,当您需要更高级的功能时,您必须使抽象不断增长。但是你应该总是从一个简单的抽象开始。

在我的工作场所,我们正在使用这种方法的更精细版本,并且效果很好。

【讨论】:

  • 您也可以只使用Pure DI 并完全跳过这个额外的抽象层。我觉得这个建议既不实用也不好。
  • -1 出于与@markseemann + 相同的原因,因为正如您自己所说的那样,这样的抽象会随着时间的推移而增长,这显然违反了 ISP
  • 我喜欢这种方法,因为它不仅可以用于 IOC 容器,还可以用于任何外部库或包。它使您可以选择在将来安全地替换它们并将它们与您的代码隔离。
【解决方案3】:

我希望这符合小的定义


using System;
using System.Linq;

namespace IOC
{
    /// <summary>
    /// Ioc Container
    /// </summary>
    public class Container
    {
        private readonly System.Collections.Generic.Dictionary<Type, Type> map = new System.Collections.Generic.Dictionary<Type, Type>();
        public string Name { get; private set; }
        public Container(string containerName)
        {
            Name = containerName;
            System.Diagnostics.Trace.TraceInformation("New instance of {0} created", Name);
        }

        /// <summary>
        /// Register the mapping for inversion of control
        /// </summary>
        /// <typeparam name="From">Interface </typeparam>
        /// <typeparam name="To">Insatnce</typeparam>
        public void Register<From,To>()
        {
            try
            {
                map.Add(typeof(From), typeof(To));
                System.Diagnostics.Trace.TraceInformation("Registering {0} for {1}", typeof(From).Name, typeof(To).Name);
            }
            catch(Exception registerException)
            {
                System.Diagnostics.Trace.TraceError("Mapping Exception", registerException);
                throw new IocException("Mapping Exception",registerException);
            }
        }

        /// <summary>
        /// Resolves the Instance 
        /// </summary>
        /// <typeparam name="T">Interface</typeparam>
        /// <returns></returns>
        public T Resolve<T>()
        {
            return (T)Resolve(typeof(T));
        }

        private object Resolve(Type type)
        {
            Type resolvedType = null;
            try
            {
                resolvedType = map[type];
                System.Diagnostics.Trace.TraceInformation("Resolving {0}", type.Name);
            }
            catch(Exception resolveException)
            {
                System.Diagnostics.Trace.TraceError("Could't resolve type", resolveException);
                throw new IocException("Could't resolve type", resolveException);
            }

            var ctor = resolvedType.GetConstructors().First();
            var ctorParameters = ctor.GetParameters();
            if(ctorParameters.Length ==0)
            {
                System.Diagnostics.Trace.TraceInformation("Constructor have no parameters");
                return Activator.CreateInstance(resolvedType);
            }

            var parameters = new System.Collections.Generic.List<object>();
            System.Diagnostics.Trace.TraceInformation("Constructor found to have {0} parameters",ctorParameters.Length);

            foreach (var p in ctorParameters)
            {
                parameters.Add(Resolve(p.ParameterType));
            }

            return ctor.Invoke(parameters.ToArray());
        }
    }
}

【讨论】:

  • “希望”不应该是程序员的感觉 ;)
猜你喜欢
  • 1970-01-01
  • 2015-06-01
  • 2020-02-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-17
相关资源
最近更新 更多