在 C# 中创建一个小型 IoC 容器

Creating a small IoC Container in C#

我决定用 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>>?如何输入解析方法的return?

这是一个 example implementation with 21 lines of code. But please don't be tempted to simplify development by implementing some dictionary that holds the registrations (other than doing so for educational purposes). There are many downsides to hand rolling your own DI library. As explained , you are much better of by applying Pure DI (which means: DI without DI library) and switching from Pure DI to a DI library later on, in case -and ONLY in case- your Composition Root 没有它就很难维护。

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

1) 为 "your dream IoC container" 设计一个抽象,只包含您需要的最低限度。像这样:

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

2) 创建一个抽象的实现,它只是将所有功能委托给现有组件。我推荐Autofac,但是海里有很多鱼。

3) 使用您的 "wrapper IoC".

开发您的应用程序

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

这种方法的优点:

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

当然,当您需要更高级的功能时,您将不得不增加抽象。但是您应该始终从简单的抽象开始。

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

我希望这符合小的定义


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());
        }
    }
}