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

时间:2015-09-14 15:07:30

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

我决定在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方法的返回值?

3 个答案:

答案 0 :(得分:9)

Here is an 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 here, 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 becomes hard to maintain without.

答案 1 :(得分:0)

I really recommend a pragmatic approach here.

1) Design an abstraction for "your dream IoC container", with only the bare minimum that you need. Something like this:

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

2) Create an implementation of your abstraction that simply delegates all the functionality to an existing component. I recommend Autofac but there's plenty of fish in the sea.

3) Develop your application using your "wrapper IoC".

4) If at some point you find that the external IoC component has performance issues (or any other type of issues), write another implementation of your IoC abstraction that uses another external component, your own code, or a combination of both. Even if your application is in an advanced state, you just have to change the small piece of code that instantiates the IoC wrapper (perhaps just one line of code).

Advantages of this approach:

  1. You use a mature and well-tested IoC container (if you choose wisely) while hiding its complexity behind a small interface. This helps improve code readability.
  2. You don't fall in the premature optimization trap.
  3. You can entirely switch from one IoC container to another one with very little impact in your existing code (if your abstraction is well designed). This wipes out (or at least minimizes) the "using libraries that I don't control" concern.

Of course, you will have to make the abstraction grow as you need more advanced functionality. But you should always start with a simple abstraction.

At my work place we are using a more elaborate version of this approach and it's working nicely.

答案 2 :(得分:0)

我希望这符合“小”的定义


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