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

Resolving a service registered on a parent container with a dependency registered in a child container throws #108

Open
tomasaschan opened this issue Nov 7, 2016 · 4 comments

Comments

@tomasaschan
Copy link

Given the service types defined at the bottom, the following code works, and prints "A bar." as expected:

var container = new TinyIoCContainer();

container.Register<IFoo, Foo>();
var child = container.GetChildContainer();

child.Register<IBar, Bar>();
var foo = child.Resolve(typeof(IFoo)) as IFoo;

Console.WriteLine(foo?.TheFoo ?? "null");

If, however, I add another service level (i.e. Foo->Baz->Qux instead of just Foo->Bar), resolving a IFoo throws an exception stating that IQux cannot be resolved:

var container = new TinyIoCContainer();

container.Register<IFoo, Foo>();
container.Register<IBar, Baz>();

var child = container.GetChildContainer();

child.Register<IQux>(new Qux());

var foo = child.Resolve(typeof(IFoo)) as IFoo; // this throws

Note that IQux is registered on the same container from which I'm trying to resolve an IFoo. If I set a breakpoint on that line and try child.Resolve<IQux>() or child.Resolve(typeof(IQux)) in a watch window, it works fine. However, when resolving the Baz instance needed to fulfill Foo's dependencies, it seems it can no longer find the Qux registration.

Am I doing something unsupported here, or is this a bug in TinyIoC?

public interface IFoo
{
    string TheFoo { get; }
}

public interface IBar
{
    string TheBar { get; }
}

public interface IQux
{
    string TheQux { get; }
}

public class Foo : IFoo
{
    private readonly IBar _bar;

    public Foo(IBar bar)
    {
        _bar = bar;
    }

    public string TheFoo => _bar.TheBar;
}

public class Bar : IBar
{
    public string TheBar { get; } = "A bar.";
}

public class Baz : IBar
{
    private readonly IQux _bar;

    public Baz(IQux bar)
    {
        _bar = bar;
    }

    public string TheBar => _bar.TheQux;
}

public class Qux : IQux
{
    public string TheQux { get; } = "A qux.";
}
@tomasaschan
Copy link
Author

After sleeping on this, and then trying again, I also realized this works:

var container = new TinyIoCContainer();

container.Register<IFoo, Foo>().AsMultiInstance();
container.Register<IBar, Baz>().AsMultiInstance();

var child = container.GetChildContainer();

child.Register<IQux>(new Qux());

var foo = child.Resolve(typeof(IFoo)) as IFoo; // this works now!

I tried to understand why by reading the documentation on lifetimes (a couple of times...) but I still don't get it. Why does this make it work?

@adbre
Copy link
Contributor

adbre commented Nov 24, 2016

The dependency to IQux exists only in the child container, so if the singleton (belonging to the parent container) is requested by the child container it could satisfy the IQux dependency, but the Qux instance would be disposed when the child container ends.

This would be very strange since a the singleton (in the parent container) suddenly would be disposed after it's dependency was disposed.

In summary, a singleton must obtain it's dependencies only from siblings (in same container) or from parents; never from children.

Edit: Yes, none of your classes are disposables, but above rule/principle is still valid.

@tomasaschan
Copy link
Author

But why doesn't this apply to the Foo -> Bar dependency in the first example?

Let's rename them, to make it more obvious - we have two cases: FooInParent -> BarInChild vs FooInParent -> BazInChild -> QuxInChild. I'm always resolving from the child container.

IIUC, the second case doesn't work because when the child container is disposed, so is the QuxInChild instance, which makes the BazInChild instance, and thereby the FooInParent instance, invalid.

For the first case, I thought that disposing the child container would also dispose BarInChild, invalidating the FooInParent instance in the same way, but apparently not. Why?

(Sorry for asking so many questions, but I'm a curious guy :) Since I know how to make my code work for now, finding out why is mostly for my own learning, and not so urgent as it was...)

@adbre
Copy link
Contributor

adbre commented Nov 25, 2016

Your first scenario, where IBar is registered in the child container, does not work. Are you sure it passes for you? I copy-pasted your code into a unit test to be certain. An exception is thrown: Unable to resolve type: IBar.

A container tracks all objects that should be disposed once the container is disposed.
A singleton is tracked in the container which it is registered (so the singleton may be re-used by multiple child containers).
A multi-instance is tracked in the container from which it was resolved.

Since the singleton is created on-demand, the singleton must always be created as if the first Resolve call was made directly on the container containing the singleton registration (parent in your case).

This is also why when using callback factories (like .Register<T>(Func<TinyIoCContainer,NamedParameterOverloads,T>) for example), you always is passed the container which originally requested the resolution. This way you can ensure your callback factory can resolve any dependencies belonging to the child containers rather than relying on the parent container.
However, using only callback factories you would not be able to reproduce your issue since a callback/delegate registration is not a singleton registration.

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

2 participants