Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One singleton for different types #461

Closed
Karnah opened this issue Nov 15, 2018 · 6 comments
Closed

One singleton for different types #461

Karnah opened this issue Nov 15, 2018 · 6 comments

Comments

@Karnah
Copy link

Karnah commented Nov 15, 2018

Good day. I use version 5.2.1 on .Net 4.7.2.
I have interface IClass and two classes BaseClass (inherit IClass) and MainClass (inherit BaseClass). I need resolve the same sigleton for all three types . I use next code:

var c = serviceContainer.Create<Class>();
serviceContainer.RegisterInstance(c);
serviceContainer.RegisterInstance<BaseClass>(c);
serviceContainer.RegisterInstance<IClass>(c);
serviceContainer.Compile();

Problem - Create() method registers Class as transient and RegisterInstance not override this. This was unexpected for me. I decided that main is first registration. Then I've wrote this:

serviceContainer.Register<Class>(new PerContainerLifetime());
var c = serviceContainer.Create<Class>();
serviceContainer.RegisterInstance<BaseClass>(c);
serviceContainer.RegisterInstance<IClass>(c);
serviceContainer.Compile();

And Create() method has overrided PerContainerLifetime()! Now I use second way, but use with TryGetInstance(). It seems to me this moment can be unobvious not only for me.

@seesharper
Copy link
Owner

Hi @Karnah

Try this

using LightInject;

var container = new ServiceContainer();
container.Register<MainClass>(new PerContainerLifetime());
container.Register<IClass>(f => f.GetInstance<MainClass>());
container.Register<BaseClass>(f => f.GetInstance<MainClass>());

var first = container.GetInstance<MainClass>();
var second = container.GetInstance<BaseClass>();

var third = container.GetInstance<IClass>();



Console.WriteLine(first == second);
Console.WriteLine(first == third);
Console.WriteLine(second == first);
Console.WriteLine(second == third);



public interface IClass
{

}

public abstract class BaseClass : IClass
{

}

public class MainClass : BaseClass
{

}

@Karnah
Copy link
Author

Karnah commented Nov 15, 2018

Thanks for quick answer!
Unfortunately, I use LightInject as IoC for Prism. I can use only this interface for registration -IContainerRegistry
It's not hard to get ServiceContainer, but I prefer work with abstraction. I just wanna discuss about registration overriding. I think it's unobvious feature. Or not?

@WolfgangKluge
Copy link

WolfgangKluge commented Jan 8, 2019

The solution is ok for non-generic types like

container.RegisterSingleton(typeof(IFoo), typeof(Foo));
container.RegisterSingleton(typeof(IFoo2), f => f.GetInstance(typeof(IFoo)));

container.GetInstance<IFoo>() == container.GetInstance<IFoo2>();

but it does not work with open generics..

container.RegisterSingleton(typeof(IFoo<>), typeof(Foo<>));
container.RegisterSingleton(typeof(IFoo2<>), f => f.GetInstance(typeof(IFoo<>)));

container.GetInstance<IFoo<string>>() == container.GetInstance<IFoo2<string>>()

container.GetInstance<IFoo2<string>>() throws an ArgumentNullException (due to the call of factory.GetInstance(typeof(IFoo<>)))..

I think it would be much more convenient, if the register-method could detect the equal implementation. This is what I would expect (and prefer) to work

container.RegisterSingleton(typeof(IFoo<>), typeof(Foo<>));
container.RegisterSingleton(typeof(IFoo2<>), typeof(Foo<>));

container.GetInstance<IFoo<string>>() == container.GetInstance<IFoo2<string>>()

@seesharper
Copy link
Owner

seesharper commented Jan 8, 2019

You can get around this problem with the RegisterFallback method.

var container = new ServiceContainer();

container.Register(typeof(FooBar<>), new PerContainerLifetime());

container.RegisterFallback((t,s) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IFoo<>), sr => {
    var fooBarType = typeof(FooBar<>).MakeGenericType(sr.ServiceType.GetGenericArguments());
    return sr.ServiceFactory.GetInstance(fooBarType);
}, new PerContainerLifetime());

container.RegisterFallback((t,s) => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IBar<>), sr => {
    var fooBarType = typeof(FooBar<>).MakeGenericType(sr.ServiceType.GetGenericArguments());
    return sr.ServiceFactory.GetInstance(fooBarType);
}, new PerContainerLifetime());

var fooBar = container.GetInstance<FooBar<int>>();

var foo = container.GetInstance<IFoo<int>>();

var bar = container.GetInstance<IFoo<int>>();

Debug.Assert(ReferenceEquals(fooBar, foo));
Debug.Assert(ReferenceEquals(fooBar, bar));


public interface IFoo<T>{}

public interface IBar<T>{}

public class FooBar<T> : IFoo<T>, IBar<T>{}

We can even put this behind an extension method like this

public static IServiceRegistry Associate(this IServiceRegistry registry, Type serviceType, Type withServiceType)
{
    registry.RegisterFallback
    (
        (t,s) => t.IsGenericType && t.GetGenericTypeDefinition() == serviceType,
        sr => sr.ServiceFactory.GetInstance(withServiceType.MakeGenericType(sr.ServiceType.GetGenericArguments())),
        new PerContainerLifetime()
    );
    return registry;
}

Then we are left with this

var container = new ServiceContainer();

container.Register(typeof(FooBar<>), new PerContainerLifetime());
container.Associate(typeof(IFoo<>), typeof(FooBar<>));
container.Associate(typeof(IBar<>), typeof(FooBar<>));

var fooBar = container.GetInstance<FooBar<int>>();

var foo = container.GetInstance<IFoo<int>>();

var bar = container.GetInstance<IFoo<int>>();

Debug.Assert(ReferenceEquals(fooBar, foo));
Debug.Assert(ReferenceEquals(fooBar, bar));

@WolfgangKluge
Copy link

That's my workaround, thanks.
I still think, the library should provide equal results for the non-generic and the open generic version - but for my use cases this is sufficient.

@seesharper
Copy link
Owner

Great 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants