Bill.Zhuang
Bill.Zhuang

Reputation: 657

How Does Parameterized Instantiation Work with SingleInstance Life Scope

According to the Parameterized Instantiation (Func<X, Y, B>) documentation:

However, if you register an object as SingleInstance() and call the Func<X, Y, B> to resolve the object more than once, you will get the same object instance every time regardless of the different parameters you pass in. Just passing different parameters will not break the respect for the lifetime scope.

How does a singleton class with different parameter constructor work?

Upvotes: 3

Views: 988

Answers (2)

Cyril Durand
Cyril Durand

Reputation: 16192

Autofac factory registration won't cache instances per parameters.

Let's try with the following type:

public class Foo
{
    public Foo(Int32 a)
    {
        this._a = a;
    }

    private readonly Int32 _a;

    public override String ToString()
    {
        return String.Format("a={0}", this._a);
    }
}

If you register it like this:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Foo>().AsSelf().SingleInstance();

Autofac will allways register a single instance, no matter how you resolve it (even if you use a Func factory):

Func<Int32, Foo> foo1Factory = scope.Resolve<Func<Int32, Foo>>();
Foo foo1 = foo1Factory(1);
Func<Int32, Foo> foo2Factory = scope.Resolve<Func<Int32, Foo>>();
Foo foo2 = foo2Factory(2);

foo1 and foo2 will be the same instance.

If you want to have only one instance per parameter, this implies that you want more than a single instance of Foo, so you can't register Foo using the SingleInstance() method. You will have to implement a custom factory that will do caching to have one instance per parameter:

public class FooFactory
{
    public FooFactory(IComponentContext componentContext)
    {
        this._componentContext = componentContext;
        this._instances = new Dictionary<Int32, Foo>();
        this._lock = new Object();
    }

    private readonly IComponentContext _componentContext;
    private readonly Dictionary<Int32, Foo> _instances;
    private readonly Object _lock;

    public Foo GetInstance(Int32 a)
    {
        Foo parameterizedFoo;
        if (!this._instances.TryGetValue(a, out parameterizedFoo))
        {
            lock (this._lock)
            {
                if (!this._instances.TryGetValue(a, out parameterizedFoo))
                {
                    Parameter aParameter = new TypedParameter(typeof(Int32), a);
                    parameterizedFoo = this._componentContext
                                           .Resolve<Foo>(aParameter);
                    this._instances[a] = parameterizedFoo;
                }
            }
        }
        return parameterizedFoo;
    }
}

You can register it this way:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Foo>().AsSelf();
builder.RegisterType<FooFactory>().AsSelf().SingleInstance();

Use the following code to use it:

FooFactory fooFactory = scope.Resolve<FooFactory>();
Foo foo = fooFactory.GetInstance(1);

But if you want to use Func<Int32, Foo> instead of FooFactory.GetInstance, you can register the method as Func<Int32, Foo>:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<Foo>().AsSelf();
builder.RegisterType<FooFactory>().AsSelf().SingleInstance();
builder.Register(c => new Func<Int32, Foo>(c.Resolve<FooFactory>().GetInstance))
       .As<Func<Int32, Foo>>()
       .SingleInstance();

You now can use this code:

Func<Int32, Foo> foo1Factory = scope.Resolve<Func<Int32, Foo>>();
Foo foo1 = foo1Factory(1);
Func<Int32, Foo> foo2Factory = scope.Resolve<Func<Int32, Foo>>();
Foo foo2 = foo2Factory(2);
Func<Int32, Foo> foo1PrimeFactory = scope.Resolve<Func<Int32, Foo>>();
Foo foo1Prime = foo1PrimeFactory(1);

foo1 and foo2 will be different whereas foo1Prime will be same as foo1.

Upvotes: 7

Bill.Zhuang
Bill.Zhuang

Reputation: 657

finally, my workmate has a similar approach

public interface IKey
{
}

public interface IKeyed<out T>
{
    T this[IKey key] { get; }
}

public class Keyed<T> : IKeyed<T>
{
    private ConcurrentDictionary<IKey, T> _dict = new ConcurrentDictionary<IKey,T>();

    T IKeyed<T>.this[IKey key]
    {
        get 
        {
            return _dict.GetOrAdd(key, k => IoC.Resolve<T>(new TypedParameter(key.GetType(), key)));
        }
    }
}

and in app start, register

   _builder.RegisterGeneric(typeof(Keyed<>))
        .As(typeof(IKeyed<>))
        .SingleInstance();

Upvotes: 0

Related Questions