westermo / FactoryGenerator

FactoryGenerator is an IoC container that uses Roslyn to prepare a container for consumption at compile-time.
MIT License
2 stars 1 forks source link

Allow for merging of different generated containers. #19

Open Iacentis opened 2 weeks ago

Iacentis commented 2 weeks ago

Use cases:

jorgensigvardsson commented 1 week ago

During compilation of a project/assembly, does the compiler expose the API surface of referenced assemblies? If it does, then it should be possible during compilation. The promise of this IoC-container is to do next to nothing at runtime. Merging containers at runtime would require weakening that promise.

Iacentis commented 1 week ago

There is an argument to be made for it being a weakening of promise, sure. And it might be out-of-scope for this.

The core concept that this is trying to deal with is:

You have .dll / .exe "A", when starting, "A" looks for other .dll files in, say, $PWD/plugins/*.dll, or some structure of the sort, i.e .dll files that were not part of the compilation that created "A" but nonetheless are loaded & used by "A", i.o.w. a plugin.

Say $PWD/plugins/B.dll contains a generated container with some set IEnumerable<C>, and "A" also contains a generated container with some set IEnumerable<C>, a 'merged' container in this case would return all (unique?) IEnumerable<C> from both B.dll's container and "A"'s container, for example.

One could possible imagine doing this by implementing something alike

//Inside AssemblyA.Generated.DependencyInjectionContainer
public static IContainer FromBase(IContainer base, /*Generated Booleans*/)
{
   var container = new DependencyInjectionContainer(/*Insert booleans*/, /*Try resolve 'bubbled up' constructor arguments from base container*/);
   container.MergeDictionary(base); //Some method to merge the resolve dictionary.
   return container;
}
jorgensigvardsson commented 1 week ago

How about exposing static metadata on the container, such that a runtime library can figure out (quickly w/o reflection) what to do in a merge?

Iacentis commented 1 week ago

That could be one way of doing it, sure.

I think that there needs to be some sort of explicit ordering or hierarchy to the "merging", for when resolving the non-enumerable cases. In essence, if both container A and container B contain an injection of interface C, then there should be a way to determine which C will be resolved by (Merged container of A & B).Resolve<C>()

One way to possible do this is to not do a merge, but to allow, in essence, a reference tree of containers, which might expand the Resolve() time somewhat, in cases with many merged containers. Consider the FromBase method up above, what if MergeDictionary simply looked something like this:

public void MergeDictionary(IContainer base)
{
    this.BaseContainer = base;
}

And then transform Resolve() into something alike

public T Resolve<T>()
{
    if(TryResolve<T>(out var resolved)) return resolved;
    if(BaseContainer != null) return BaseContainer.Resolve<T>();
    throw new /*Explanatory exception*/
}

This would allow chaining them together in a pretty clear order. Might not be the most optimal for memory performance though, since it would need to keep all containers in-memory.