3

I am using StructureMap 4.6 as my IoC Container. I am a bit confused about its lifecycles. As I have read in its documentation, Transient will create a single instance of the object per container. Supported Lifecycles

I am checking this scenario by creating a simple Console Application project. My code is as below:

Program.cs

class Program
{
    private static IContainer _Container;
    static void Main(string[] args)
    {
        _Container = Container.For<ConsoleRegistry>();

        var serv1 = _Container.GetInstance<IFileService>();
        Console.WriteLine($"Name: {_Container.Name}");
        Console.WriteLine(serv1.GetUniqueID());

        var serv2 = _Container.GetInstance<IFileService>();
        Console.WriteLine($"Name: {_Container.Name}");
        Console.WriteLine(serv2.GetUniqueID());

        Console.ReadKey();
    }
}

ConsoleRegistry.cs

public class ConsoleRegistry : Registry
{
    public ConsoleRegistry()
    {
        Scan(_ =>
        {
            _.TheCallingAssembly();
            _.WithDefaultConventions();
        });
    }
}

IFileSerivce.cs

public interface IFileService
{
    string Read(string path);

    void Write(string path, string content);

    bool FileExists(string path);

    string GetUniqueID();
}

FileService.cs

public class FileService : IFileService
{
    private static int instanceCounter;
    private readonly int instanceId;

    public FileService()
    {
        this.instanceId = ++instanceCounter;
        Console.WriteLine("File Service is Created.");
    }

    public int UniqueID
    {
        get { return this.instanceId; }
    }

    public string GetUniqueID()
    {
        return UniqueID.ToString();
    }

    public string Read(string path)
    {
        return File.ReadAllText(path);
    }

    public void Write(string path, string content)
    {
        File.WriteAllText(path, content);
    }

    public bool FileExists(string path)
    {
        return File.Exists(path);
    }
}

When I run the application the result is:

Output

My question is when I resolve an instance of IFileService, I have expected to get a single instance of FileService per container. But, as you can see it gives two different instances. Why is that the case?

NightOwl888
  • 55,572
  • 24
  • 139
  • 212
Navid K
  • 90
  • 1
  • 6

1 Answers1

5

Your understanding of the documentation is incorrect.

  • Transient -- The default lifecycle. A new object is created for each logical request to resolve an object graph from the container.
  • Singleton -- Only one object instance will be created for the container and any children or nested containers created by that container

You are using Transient, which means you get an instance every time Resolve is called.

But the behavior you are describing is for Singleton, which means to create the instance only the first time Resolve is called.

To get the behavior you want, you have to change the registration type to Singleton.

public class ConsoleRegistry : Registry
{
    public ConsoleRegistry()
    {
        Scan(_ =>
        {
            _.TheCallingAssembly();
            _.With(new SingletonConvention<IFileService>());
            _.WithDefaultConventions();
        });
    }
}

internal class SingletonConvention<TPluginFamily> : IRegistrationConvention
{
    public void Process(Type type, Registry registry)
    {
        if (!type.IsConcrete() || !type.CanBeCreated() || !type.AllInterfaces().Contains(typeof(TPluginFamily))) return;

        registry.For(typeof(TPluginFamily)).Singleton().Use(type);
    }
}

Reference: How can I configure Structuremap to auto scan type in Assembly and Cache by Singleton?

Singleton VS Transient Example

The easiest way to think about this is by showing an example without using a DI container.

Services

Here we have a service and an application service. The only real difference here is that the application service is intended to be the entire application graph.

public class Service
{
    public string Name { get; private set; } = Guid.NewGuid().ToString();
}

public class Application
{
    private readonly Service singleton;
    private readonly Service transient;

    public Application(Service singleton, Service transient)
    {
        this.singleton = singleton;
        this.transient = transient;
    }

    public Service Singleton { get { return singleton; } }
    public Service Transient { get { return transient; } }
}

Container

In our container, we register 2 instances of Service, one singleton and one transient. The singleton is only instantiated once per container instance. The transient is instantiated every time Resolve is called.

public class MyContainer
{
    private readonly Service singleton = new Service();

    public Application Resolve()
    {
        return new Application(
            singleton: this.singleton, 
            transient: new Service());
    }
}

Usage

In a real world application, there would only be one instance of Application. However, we are showing two Application instances to demonstrate that a service registered as Singleton will be the same instance for the same container instance. A transient will be created each time Resolve is called.

class Program
{
    static void Main(string[] args)
    {
        var container = new MyContainer();

        var application1 = container.Resolve();
        var application2 = container.Resolve();


        Console.WriteLine($"application1.Transient.Name: {application1.Transient.Name}");
        Console.WriteLine($"application2.Transient.Name: {application2.Transient.Name}");
        Console.WriteLine();
        Console.WriteLine($"application1.Singleton.Name: {application1.Singleton.Name}");
        Console.WriteLine($"application2.Singleton.Name: {application2.Singleton.Name}");

        Console.ReadKey();
    }
}

Output

application1.Transient.Name: dc134d4d-75c8-4f6a-a1a5-367156506671 application2.Transient.Name: f3012ea2-4955-4cfa-8257-8e03a00b1e99

application1.Singleton.Name: 86d06d7d-a611-4f57-be98-036464797a41 application2.Singleton.Name: 86d06d7d-a611-4f57-be98-036464797a41

Community
  • 1
  • 1
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • Thank you, but I am confused a little bit because of this line in documentation: 'The easiest way to think of Transient is that a single object instance will be created for each top level call to Container.GetInstance() (or any other object resolution method on the IContainer interface).' Could you please explain it to me? What is the difference between this and Singletone? – Navid K Mar 08 '18 at 07:55
  • I added an example to the answer. – NightOwl888 Mar 08 '18 at 10:07