Click here to Skip to main content
15,868,016 members
Articles / Programming Languages / C#

Making Simple.IoC Even More Simple

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
6 Nov 2011LGPL33 min read 7K   4  
The following code represents most of LinFu.IoC's functionality when managing service instances.

Believe it or not, the following code represents most of LinFu.IoC's functionality when managing service instances:

C#
// The SimpleContainer will handle unnamed services
public class SimpleContainer : IContainer
{
   private readonly Dictionary<type,> _factories = new Dictionary<type,>();  

   public virtual bool SuppressErrors
   {
       get; set;
   }

   public virtual void AddFactory(Type serviceType, IFactory factory)
   {
       _factories[serviceType] = factory;
   }

   public virtual bool Contains(Type serviceType)
   {
       return _factories.ContainsKey(serviceType);
   }

   public virtual object GetService(Type serviceType)
   {
       object result = null;
       if (!_factories.ContainsKey(serviceType) && !SuppressErrors)
           throw new ServiceNotFoundException(serviceType);

       if (!_factories.ContainsKey(serviceType) && SuppressErrors)
           return null;
 
       // Use the corresponding factory
       // and create the service instance
       var factory = _factories[serviceType];
       if (factory != null)
           result = factory.CreateInstance(this);

       return result;
   }
}

// The named container will handle named services
public class NamedContainer : SimpleContainer, INamedContainer
{
   protected readonly Dictionary<string,>> _namedFactories =
       new Dictionary<string,>>();

   public virtual void AddFactory(string serviceName, 
                  Type serviceType, IFactory factory)
   {
       if (serviceName == string.Empty)
       {
           AddFactory(serviceType, factory);
           return;
       }
       // Create the entry, if necessary
       if (!_namedFactories.ContainsKey(serviceName))
           _namedFactories[serviceName] = new Dictionary<type,>();

       _namedFactories[serviceName][serviceType] = factory;      
   }

   public virtual bool Contains(string serviceName, Type serviceType)
   {
       // Use the standard IContainer.Contains(Type)
       // if the service name is blank
       if (serviceName == string.Empty)
           return Contains(serviceType);

       return _namedFactories.ContainsKey(serviceName) &&
              _namedFactories[serviceName].ContainsKey(serviceType);
   }

   public virtual object GetService(string serviceName, Type serviceType)
   {
       // Used the other GetService method if
       // the name is blank
       if (serviceName == string.Empty)
           return GetService(serviceType);

       // Determine if the service exists, and
       // suppress the errors if necessary
       bool exists = Contains(serviceName, serviceType);
       if (!exists && SuppressErrors)
           return null;

       if (!exists && SuppressErrors != true)
           throw new NamedServiceNotFoundException(serviceName, serviceType);

       var factory = _namedFactories[serviceName][serviceType];

       // Make sure that the factory exists
       if (factory == null)
           return null;

       return factory.CreateInstance(this);
   }
}

As you can see, there's nothing special about this code. Some might even scoff at it since it's too minimalistic to be useful. After all, one might say that an IoC container has far more responsibilities than just object instantiation. Ninject, for example has features such as contextual binding and method interception. Surely this isn't all there is to LinFu.IoC's features, is it?

Bend it like Ockham

Despite the varying complexity of most (if not all) IoC containers in the field, logically speaking, the code listed above is the absolute minimum amount of code necessary to separate the instantiation of a service instance from its actual client code. When you request a service instance from a given IoC container (whether it be LinFu, Ninject or countless other containers out there), there has to be some point where the container has to decide if that service instance can be created, as well as manage the lifetime of that service once it is already out of the container. As most of us IoC container developers know, there's quite a lot more to a container than just managing the lifetime of its individual services. Creating the instance is only the first step, and I'll show you how LinFu version 2's new IoC container will implement the some of the same features without sacrificing its relatively-horizontal learning curve.

The Pattern of Other Containers

Typically, these containers will use either property setter injection or constructor injection to autowire together all of the dependencies that an application might need during its lifetime. In addition, they might implement additional features such as interception, logging, and AOP. However, despite the differences in features among containers, there are two points where these features are commonly applied:

  • When a service is about to be created (e.g. constructor injection) or,
  • When a service is already instantiated (e.g. property setter injection, or interception)

This implies that a developer can add additional features to their respective IoC container simply by controlling the point where the service is going to be created as well as controlling the point where the service has been recently instantiated. In fact, if you can isolate those two points from the rest of the container, you can effectively add new features without affecting the rest of the code.

Now the reason why LinFu's IoC container can get away with such simple code is because it actually delegates its factory methods to an instance of the IFactory interface:

C#
public interface IFactory
{
    object CreateInstance(IContainer container);
}

LinFu's IoC container uses each factory instance to determine how a service implementation should be instantiated, and each factory instance, in turn, is responsible for managing the lifetime of each component that it creates. With that in mind, the only thing that you have to do to extend LinFu's IoC container is to control how each factory creates its object instances (such as deciding which constructor and constructor arguments to use when implementing constructor injection) and control the instances that come out of each factory.

For example, if I wanted to add interception to LinFu.IoC, all I have to do is wrap a Decorator around an IContainer (or INamedContainer) instance that wraps each service instance in a proxy:

C#
public class ContainerDecorator : IContainer
{
  private IContainer _container;
  public ContainerDecorator(IContainer realContainer)
  {
       _container = realContainer;
  }
  // Other methods skipped for brevity
  public object GetService(Type serviceType);
  {
       // Grab the service instance
       var result = container.GetService(serviceType);
    
       // Wrap the instance, if possible
       if (result != null)
            return SomeProxyFactory.Wrap(result);

       // Otherwise return the original instance
       return result;
  }
}

...and in the client code, using the additional decorator is as easy as:

C#
var container = new ContainerDecorator(new SimpleContainer());

// Use the container somehow and transparently use the decorator to wrap the
// result
var service = container.GetService<ISomeServiceType>();

// ...

As you can see, the implementation of LinFu.IoC is very straightforward, and there's really nothing exotic about the design. For those of you who were probably wondering why LinFu.IoC isn't using generics, here's the list of extension methods that completes the design:

C#
public static class ContainerExtensions
{
  public static T GetService<t>(this IContainer container)
      where T : class
  {
      var serviceType = typeof (T);
      return container.GetService(serviceType) as T;
  }
  public static T GetService<t>(this INamedContainer 
         container, string serviceName)
         where T : class
  {
      return container.GetService(serviceName, typeof (T)) as T;
  }
  public static void AddFactory<t>(this INamedContainer container, 
         string serviceName, IFactory<t> factory)
  {
      IFactory adapter = new FactoryAdapter<t>(factory);
      container.AddFactory(serviceName, typeof (T), adapter);
  }

  public static void AddFactory<t>(this IContainer container, 
                IFactory<t> factory)
  {
      IFactory adapter = new FactoryAdapter<t>(factory);
      container.AddFactory(typeof(T), adapter);
  }
  public static void AddService<t>(this IContainer container, T instance)
  {
      container.AddFactory(typeof (T), new InstanceFactory(instance));
  }
}

Again, there's nothing unconventional in the design. In the end, it's the simplicity that matters most, and that is the difference that LinFu.IoC offers.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Software Developer (Senior) Readify
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 --