Ninject and Singletons: How To Ensure One Instance per Variation of Activation Parameters

An entry about inversion of control Publication date 10. October 2008 18:09

Working on a pet project (that I hopefully will be able to unveil soon) which uses Ninject as its Inversion of Control container, I came upon a need to ensure that there only ever existed a single instance of a type, per variation of transient parameters in the activation context. An example will help clarify…

Say for instance that you have a class like this:

public class Test
{
    private static int InstanceCount = 0;
 
    public Test(string param)
    {
        InstanceId = ++InstanceCount;
    }
 
    public int InstanceId;
}

 

Now, assume we execute the following code:

var test1 = kernel.Get<Test>(With.Parameters.ConstructorArgument("param", "1"));
var test2 = kernel.Get<Test>(With.Parameters.ConstructorArgument("param", "2"));
var test3 = kernel.Get<Test>(With.Parameters.ConstructorArgument("param", "1"));
 
Console.WriteLine(test1.InstanceId);
Console.WriteLine(test2.InstanceId);
Console.WriteLine(test3.InstanceId);
Console.ReadLine();

 

What I want to happen here, is for the kernel to create two instances of my Test class, so that test1 and test2 test3 are the same because they passed the same transient parameter to the activation context. The output of the app then should be:

1
2
1

Notice that the first and third lines indicate that those two objects are the same instance. How can we make this happen? By creating the following binding for our Test class:

Bind<Test>().ToSelf().Using<SingletonPerVariationOfParametersBehavior>())

 

The SingletonPerVariationOfParametersBehavior (f you have an idea for a shorter name that still explains what it does let me know!) class is a custom behavior, which thanks to the great API that Ninject has only took like 10 minutes to implement. It looks like this:

/// <summary>
/// A behavior which ensures a single instance of a type exist, per variation of 
/// transient parameters in the activation context, throughout the application.
/// The variation is determined by combining the hashcodes of the implementation 
/// type and the hashcodes of transient parameter values.
/// </summary>
public class SingletonPerVariationOfParametersBehavior : BehaviorBase
{
    /// <summary>
    /// Initializes a new instance of the 
    /// <see cref="SingletonPerVariationOfParametersBehavior"/> class.
    /// </summary>
    public SingletonPerVariationOfParametersBehavior()
    {
        SupportsEagerActivation = false;
        ShouldTrackInstances = true;
    }
 
    private readonly Dictionary<int, IContext> _contextCache = new Dictionary<int, IContext>();
 
    ///<summary>
    ///Resolves an instance of the type based on the rules of the behavior.
    ///</summary>
    ///<param name="context">The activation context.</param>
    ///<returns>
    ///An instance of the type associated with the behavior.
    ///</returns>
    public override object Resolve(IContext context)
    {
        Ensure.NotDisposed(this);
 
        int hash = BuildHashForContext(context);
 
        IContext cachedContext;
 
        if (!_contextCache.TryGetValue(hash, out cachedContext))
        {
            lock (_contextCache)
            {
                if (!_contextCache.TryGetValue(hash, out cachedContext))
                {
                    context.Binding.Components.Get<IActivator>().Activate(context);
                    _contextCache.Add(hash, context);
                    cachedContext = context;
                }
            }
        }
 
        return cachedContext.Instance;
    }
 
    /// <summary>
    /// Builds a unique hash code for the context based on the implementation 
    /// type plus the transient activation parameters
    /// </summary>
    /// <param name="context">The activation context</param>
    private static int BuildHashForContext(IContext context)
    {
        int hashCode = context.Implementation.GetType().GetHashCode();
 
        foreach (Type type in context.Parameters.GetTypes())
        {
            foreach (IParameter param in context.Parameters.GetAll(type))
            {
                object value = param.GetValue(context);
 
                if (null != value) hashCode = hashCode ^ value.GetHashCode();
            }
        }
 
        return hashCode;
    }
 
    ///<summary>
    /// Releases the instance of the type contained in the context based 
    /// on the rules of the behavior.
    ///</summary>
    ///<param name="context">The activation context.</param>
    public override void Release(IContext context)
    { }
 
    ///<summary>
    /// Releases all resources held by the object.
    ///</summary>
    ///<param name="disposing"><see langword="True" /> if managed objects 
    /// should be disposed, otherwise <see langword="false" />.</param>
    protected override void Dispose(bool disposing)
    {
        if (disposing && !IsDisposed)
        {
            DestroyAll(_contextCache.Values);
            _contextCache.Clear();
        }
 
        base.Dispose(disposing);
    }
}

 

Disclaimer: Don’t trust me to have written this code without any bugs or performance bottlenecks - it’s fresh out of the oven.

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

Powered by BlogEngine.NET 1.4.5.0

Welcome!

My name is Fredrik Kalseth, and this is my blog - thanks for visiting! I am fortunate enough to work with what I love for a living, and this blog is essentially the biproduct of that.

I work as a senior consultant for Capgemini, and am also an active participant in the Norwegian .NET community, as an avid attendee but also as a speaker (most recently at NNUG and MSDN Live).

As a developer, I have a wide circle of interest. My primary passion is for agile, test-driven development, with focus on best practices and clean code. That said, I also love to work on the frontend, especially with web development.

On Twitter? My handle is fkalseth. On LinkedIn? I`m there too.

NDC 2010

The conference to attend this summer happens June 16th-18th in Oslo, Norway. Are you going? Be sure to catch my talk on AOP while you're there!

 

Disclaimer

This is a personal blog; any opinions expressed here are my own and do not necessarily reflect those of my employer. All content herein is my own original creation, and as such is protected by copyright law. Unless otherwise stated, all source code posted on this blog is freely usable under the Microsoft Permissive License.

What Readers Talk About

Comment RSS