Evelie
Evelie

Reputation: 3059

Ninject InSingletonScope Creating multiple instances

I have a very simple test project where I try tell ninject that my ILoader instance should be a singleton. No matter what I do it creates multiple instances of it.

Simple interface.

public interface ILoader
{
    IEnumerable<int> Get();
}

Implementation for test purpose

public class TestLoader : ILoader
{
    private IEnumerable<int> _Datasource;

    public void Set(IEnumerable<int> enumerable)
    {
        _Datasource = enumerable;
    }
    public IEnumerable<int> Get()
    {
        return _Datasource;
    }
}

Class that depends on it

public class TestClass
{
    private ILoader _loader;
    public TestClass(ILoader loader)
    {
        _loader = loader;
    }

    public void Init()
    {
        foreach (var i in _loader.Get())
            Console.WriteLine(i);
    }
}

Module

public class TestModule : NinjectModule
{
    public override void Load()
    {
        Bind<ILoader>().To<TestLoader>();
        Bind<TestLoader>().ToSelf().InSingletonScope();
    }
}

And run it.

class Program
{
    static void Main(string[] args)
    {
        var kernel = new StandardKernel(new TestModule());

        var ds = new List<int> { 1, 2 };
        kernel.Get<TestLoader>().Set(ds);

        var tc = kernel.Get<TestClass>();
        tc.Init();
        Console.ReadLine();
    }
}

Here I want to preload my loader with testdata and the ninject should inject that very same loader into my TestClass. However it creates a new instance which is not really the desired behaviour.

I guess there are ways to work around this. But then what is the purpose of InSingletonScope? Shouldnt I be able to tell ninject that I want one and only one instance of ILoader.

Upvotes: 1

Views: 3979

Answers (1)

Yacoub Massad
Yacoub Massad

Reputation: 27861

Instead of having the Set method, you should use constructor injection (see this question) like this:

public class TestLoader : ILoader
{
    private IEnumerable<int> _Datasource;

    public TestLoader(IEnumerable<int> enumerable)
    {
        _Datasource = enumerable;
    }
    public IEnumerable<int> Get()
    {
        return _Datasource;
    }
}

And then here is how you would register it and resolve it:

static void Main(string[] args)
{
    var kernel = new StandardKernel();

    var ds = new List<int> { 1, 2 };

    kernel
        .Bind<ILoader>()
        .To<TestLoader>()
        .InSingletonScope()
        .WithConstructorArgument("enumerable", ds);

    var tc1 = kernel.Get<TestClass>();
    var tc2 = kernel.Get<TestClass>();
    tc1.Init();
    tc2.Init();

    Console.ReadLine();
}

In this example, the two instance of TestClass will get the same instance of TestLoader injected into them.

If for some reason you don't want to use constructor injection and you want to keep the Set method, you can do this:

static void Main(string[] args)
{
    var kernel = new StandardKernel();

    var ds = new List<int> { 1, 2 };

    kernel
        .Bind<ILoader>()
        .To<TestLoader>()
        .InSingletonScope();


    ((TestLoader)kernel.Get<ILoader>()).Set(ds);

    var tc1 = kernel.Get<TestClass>();
    var tc2 = kernel.Get<TestClass>();
    tc1.Init();
    tc2.Init();

    Console.ReadLine();
}

Upvotes: 1

Related Questions