Click here to Skip to main content
15,886,199 members
Articles / Programming Languages / C#

Simple Dependence Injector

Rate me:
Please Sign up or sign in to vote.
4.60/5 (3 votes)
5 Jan 2016CPOL3 min read 8.5K   2  
How to Build a Simple Dependence Injector from scratch using C#

Introduction

This article will guide you through creating a simple Dependency Injector 

Background

I wanted to create a language that was like javascript but without the little annoyances that bothered me. I wanted to have multiple inheritence, test, error handling and a REPL. In order to do this i had to create a many things my self as i did not want to use any existing libraries so that i could have a better understanding of how it worked. so first thing i had to build was a Depdendance Injector so here it is.

What Is It

What is a Depdence Injector

well a depdence injector allows you to ask for an interface or abstract class and be given back an implementation of that interface or abstract without knowing any details about that class. in fact using a dependance injector you can even write that class in a different library which is not referenced by the class using it.

But why would i do that, or want to do that

Because it means that you can completely decouple your code from all but the interfaces. which in turn would allow you enchance code in different library, meaning once code is compiled into a library it never needs to be edited again and you can still enhance you application. 

it also means you can easily test an object independant of all other objects by using Mocks and telling the injector to return a mock.

It Keeps all your code clean and easy to maintain by replacement.

How Does it Work

It functions like a dictionary or a look up, you ask for something by passing a type and it returns something that can be cast as that type. most of the time it would be an interface you are asking for.

so Lets say you have a type IPerson which has a string property Name

the client code doesnt need to know anything about how it implements Name it just needs to know that it does.

this allows you to obey the Dictum "Always Code to Interface" Literally.

it also means that you can create a dummy to test your code whilst someone else is writting a propper implementation of IPerson.

so each object have in its constructor a paramater that is the interface of the injector that is used in the client app

i.e.

C#
public interface IPerson {string Name {get;set;}}

and the a concrete version of that Interface / Abstract would look like

C#
public internal class DummyPerson : IPerson
{
   private IInjector Injector {get; set;}
   public DummyPerson(IInjector injector)
   {
      Injector = injector;
   }
   public string Name {get;set;}
}

and somewhere in the code you would need to register through the IInjector interface

Injector.Add(typeof(IPerson), typeof(DummyPerson));

and then when someone else where to ask for an IPerson they would get and instance of DummyPerson

i.e.

IPerson thisGal = Injector.Get(typeof(IPerson))

which would look up in the dictionary for the type registered against the type IPerson which in this case would be DummyPerson. but the constructor for Dummy Person is not empty it has Parameters, so what happens is that the Injector uses reflection to inspect those parameters and find the concrete versions of each parameter type.

C#
var Params = this.Type.GetConstructors()[0].GetParameters();
var Instances = Params.Select(p => GetInstance(p.ParameterType, passedParameters)).ToArray();
if (<span style="font-size: 9pt;">constructorParameters.Length > 0 )
{
</span><span style="font-size: 9pt;">   return Activator.CreateInstance(Type, constructorParameters)
}
else
{
</span><span style="font-size: 9pt;">    Activator.CreateInstance(Type);
</span><span style="font-size: 9pt;">}</span>

Which relies on the Function GetInstance Below, which makes the whole operation Recursive as for each parameter it looks up the type and asks the injector to determine the concrete class, inspects its first constructor and then does the same for all that functions parameters

private object GetInstance(Type t, IEnumerable<object> parms)
          {
              var enumerable = parms as object[] ?? parms.ToArray();
              if (enumerable.Length &lt; 0)
              {
                  var passedObject = enumerable.FirstOrDefault(p => p.GetType() == t);
                  if (passedObject == null)
                  {
                      return Injector.Get(t, enumerable);
                  }
                  else
                  {
                      return passedObject;
                  }
              }
              else
              {
                  return Injector.Get(t, enumerable);
              }
          }

 

Code

What i came up with is below - it is far from perfect but it works

C#
// the class is internal so i can use an interface and an adapter to get it to self inject

internal class InternalInjector 
    {
        //the basic idea is i want a type dictionary where i ask for one type and get another
        private Dictionary<Type, Record> _dict = new Dictionary<Type, Record>();

        //this allows me to override the automatic injection of a class in a Parent Child Relationship
        public T Get<T>(IEnumerable<object> parms)
        {
            return (T)Get(typeof(T), parms);
        }
        // locates the associated concrete with abstract asked for and returns it or 
        // Throws an error about not being able to find it
        public object Get(Type t, IEnumerable<object> parms)
        {
            Record rtn;
            if (!_dict.TryGetValue(t, out rtn)) throw new TypeInitializationException("Failes to Create Type " + t.ToString() + " as there is No Registered Concrete for that Abstaract", new Exception("Record Not Found"));
            return rtn.GetInstance(parms);
        }
        public object Get(Type t) { return _dict[t].Instance; }

        private Record Find(Type t)
        {
            Record rtn = null;
            if (!_dict.TryGetValue(t, out rtn))
            {
                rtn = new Record(this);
                _dict.Add(t, rtn);
            }
            return rtn;
        }
        public T Get<T>()
        {
            return (T)_dict[typeof(T)].Instance;
        }
        //adds a record to the injector
        public void Add(Type interf, Type conc)
        {
            var rtn = Find(interf);
            rtn.Type = conc;
            rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;
        }
        public void AddMethod(Type interf, Type conc, MethodInfo method)
        {
            var rtn = Find(interf);
            rtn.Type = conc;
            rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;
            rtn.Method = method;

        }
        //adds an instance (its like a singleton except its instantiated outside the injector
        public void AddInstance(Type interf, Type conc, object instnc)
        {
            var rtn = Find(interf);
            rtn.Type = conc;
            rtn.LifeCycle = Record.LifeCycleEnumeration.Singleton;
            rtn.Instance = instnc;
        }
        public void Add(Type interf, Type conc, Record.LifeCycleEnumeration lc)
        {
            var rtn = Find(interf);
            rtn.Type = conc;
            rtn.LifeCycle = lc;

        }
        public void Add<TI, TC>()
        {
            var rtn = Find(typeof(TI));
            rtn.Type = typeof(TC);
            rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;

        }
        public void Add<TI, TC>(MethodInfo method)
        {
            var rtn = Find(typeof(TI));
            rtn.Type = typeof(TC);
            rtn.LifeCycle = Record.LifeCycleEnumeration.InstancePerCall;

            rtn.Method = method;
        }
        public void Add<TI, TC>(Record.LifeCycleEnumeration lifeCycle)
        {
            var rtn = Find(typeof(TI));
            rtn.Type = typeof(TC);
            rtn.LifeCycle = lifeCycle;
        }
        public void Add<TI, TC>(MethodInfo method, Record.LifeCycleEnumeration lifeCycle)
        {
            var rtn = Find(typeof(TI));
            rtn.Type = typeof(TC);
            rtn.LifeCycle = lifeCycle;
            rtn.Method = method;
        }

        private object ReturnAndRemove(object itm, List<object> items)
        {
            items.Remove(itm);
            return itm;
        }
        // record is a hosting class for the return type which also details how it is to be created
        internal class Record
        {
            private InternalInjector Injector { get; set; }

            public MethodInfo Method
            {
                get { return _method; }
                set
                {
                    _method = value;
                    UsesConstructor = (Method == null);
                }
            }

            private bool UsesConstructor = true;
            public enum LifeCycleEnumeration
            {
                Singleton,
                InstancePerCall
            }
            public LifeCycleEnumeration LifeCycle { get; set; }
            public Type Type { get; set; }
            private object _instance = null;
            private MethodInfo _method;

            public Record(InternalInjector injector)
            {
                Injector = injector;
            }
            //this is where the automatic injection magic is
            public object CreateInstance(IEnumerable<object> parms)
            {

                var passedParameters = parms.ToArray();
                if (UsesConstructor)
                {
                    var constructorParameters = this.Type.GetConstructors()[0].GetParameters().Select(p => GetInstance(p.ParameterType, passedParameters)).ToArray();
                    return constructorParameters.Length > 0 ? Activator.CreateInstance(Type, constructorParameters) : Activator.CreateInstance(Type);
                }
                else
                {
                    var methodParameters = this.Method.GetParameters().Select(p => GetInstance(p.ParameterType, passedParameters)).ToArray();
                    return Method.Invoke(this, methodParameters);
                }
            }

            private object GetInstance(Type t, IEnumerable<object> parms)
            {
                var enumerable = parms as object[] ?? parms.ToArray();
                if (enumerable.Length > 0)
                {
                    var passedObject = enumerable.FirstOrDefault(p => p.GetType() == t);
                    if (passedObject == null)
                    {

                        return Injector.Get(t, enumerable);
                    }

                    else
                    {
                        return passedObject;
                    }
                }
                else
                {
                    return Injector.Get(t, enumerable);
                }
            }

            public object CreateInstance()
            {
                return CreateInstance(new object[] { });
            }

            public object GetInstance(IEnumerable<object> parms)
            {
                return LifeCycle == LifeCycleEnumeration.InstancePerCall ? CreateInstance(parms) : _instance ?? (_instance = CreateInstance(parms));
            }
            public object Instance
            {
                get { return LifeCycle == LifeCycleEnumeration.InstancePerCall ? CreateInstance() : _instance ?? (_instance = CreateInstance()); }
                set { _instance = value; }
            }

        }

    }

 

 

Points of Interest

This Actual Code is used as part of a project Called "Baik Programming Language at Code Plex 

  • https://baik.codeplex.com/

In a Future Article i will write how to write your own scipring language from scratch and include a reference to this article.

History

  • Initial Version Published 2016-01-06 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --