ipjohnson / Grace

Grace is a feature rich dependency injection container library
MIT License
336 stars 33 forks source link

Passing instance type in ExtraData is ignored #273

Closed dtkujawski closed 3 years ago

dtkujawski commented 3 years ago

I'm not sure if I am doing this correctly. When I pass value types (string, int, etc.) as "extraData" to the Locate method, I can see that it is given to the constructor. When I pass a class, the locate method ignores it and just creates the object again. I'm trying to pass custom instances of objects to the constructor while using Grace DI. I can do this with ActivatorUtilities.CreateInstance but the performance is much slower than locate, so I thought maybe there was another way using Grace DI directly.

Example classes (Top and Sub):

public class Sub { }

public class Top
{
    public Sub Child { get; }
    public Top(Sub child) { Child = child; }
}

I tried to pass a custom instance of Sub to the locate method but it ignored it:

    var container = new DependencyInjectionContainer();
    container.Configure(c => c.Export<Top>());

    var sub = new Sub();

    var top = container.Locate<Top>(new { sub });
    if (top.Child != sub) { throw new Exception(); }  //always throws exception

Using Microsoft's ActivatorUtilities.CreateInstance does work, but this is very slow compared to Grace Locate so I was trying to find the "right way":

    var container = new DependencyInjectionContainer();
    container.Configure(c => c.Export<Top>());

    var sub = new Sub();

    var top = ActivatorUtilities.CreateInstance<Top>(container, sub);
    if (top.Child != sub) { throw new Exception(); }  //does NOT throw exception, works
ipjohnson commented 3 years ago

The container is actually auto resolving Sub as it's concrete and there's a way to construct it.

You can try black listing the class or namespace in the container.

ipjohnson commented 3 years ago

I've added a test to show how you can exclude a specific type from auto registration.

dtkujawski commented 3 years ago

I see what you are talking about, the expression that is being created in Grace DI is "new Top(new Sub)". For performance, the expression is cached (obviously) so it doesn't know if I am calling with or without extraData. I was thinking that it might cache different expressions based on the parameters being called with extraData but I can see that would get onerous depending on all the different expressions which would be cached in that instance.

The ExcludeTypeFromAutoRegistration does indeed work, thank you so much for suggesting this alternative. Also, I found the "AutoRegisterUnkown" flag which works as well, but globally.

However, this will also block calling Locate<Sub>() directly. This brings up a quandary, is there a way to allow the expression to be created and executed on the top-level Locate call but not for child instance calls?

I know this is an edge case, but I'm exploring different scenarios in order to decide on a recommendation for a DI container replacement. I really love the performance and everything with Grace but the developers I work with have these odd patterns here and there which would need to be refactored so I'm trying to limit impact. I'm debating whether to just have them use ActivatorUtilities.CreateInstance for times where parameters are passed since that's just the 2% cases.

ipjohnson commented 3 years ago

There isn't anything out of the box but I think you could probably do something specific say using ExportFactory using the IInjectionContext and looking at what scope you're in and doing something different based on if you're in the root scope or a child scope.

My thinking on this was that the container would never be fast enough or generic enough to do runtime conditional logic well (NInject style) so I always planned for these little gaps to be filled by a developer using the ExportFactory. Hopefully it will be enough for you situation.