compile time dependency injection for .NET
Compile Time Dependency Injection For C#
Logo kindly contributed by @onelioubov and @khalidabuhakmeh
Visual Studio 16.8 or greater
.NET 5.0.102 SDK or greater
https://www.nuget.org/packages/StrongInject/
``
To use StrongInject, you first need to tell StrongInject the top-level services you would like to resolve. You do this by adding a new class implementing IContainer. This will be your container. If you want to resolve multiple top-level services, then you can implement IContainer multiple times on one container, or create multiple containers. StrongInject will then check at compile time that you've registered everything you need with the container to enable it to resolve all the top-level services. If you haven't, the compilation will fail with an error explaining what's gone wrong.
For example, if you want to resolve MyApp you might try doing this:
using StrongInject;
public class MyService {}
public class MyApp { public MyApp(MyService myService) {} }
[Register(typeof(MyApp))]
public partial class MyContainer : IContainer {}When you try compiling, it will fail with the following error: SI0102: Error while resolving dependencies for 'MyApp': We have no source for instance of type 'MyService'.
Now you fix it by adding a registration for MyService:
using StrongInject;
public class MyService {}
public class MyApp { public MyApp(MyService myService) {} }
[Register(typeof(MyApp))]
[Register(typeof(MyService))]
public partial class MyContainer : IContainer {}And this time when you compile, it will succeed and generate all the code needed to resolve an instance of MyApp at compile time.
What do I mean by a top-level service?
When using an IOC container, sometimes you request an instance from the container directly. These are top-level services. Most of the time the container resolves something though, you never ask for it explicitly - instead it's needed as a dependency for something else, which may itself be a dependency or a top-level service etc.
Ideally you want IOC containers to be non invasive - this means you write all your code as if there was no container, and then just use the container once to bootstrap your code. When writing code like this there should only ever be one top-level service. Sometimes this is not possible - for example when integrating with ASP.NET Core your controllers will usually need to be top-level services, but you should always try to minimize the number of top-level services where possible.
The next section will go into more detail about exactly how to register stuff with containers, and how to use them.
The wiki is currently a work in progress. It aims to give a more thorough formal overview of everything in StrongInject, whereas this section of the readme gives a briefer overview relying heavily on examples. I would read through this first, then check out the wiki if you have any questions.
Check out these sample projects to help you get started:
To create a container for a specific type, declare your a partial class and implement StrongInject.IContainer:
using StrongInject;
public class A {}
[Register(typeof(A))]
public partial class Container : IContainer {}If it's possible to resolve the type parameter StrongInject will generate the implementation of IContainer for you. Else it will produce an error diagnostic.
You can implement IContainer for different values of T. They will all share SingleInstance dependencies.
using StrongInject;
public class A {}
public class B {}
[Register(typeof(A))]
[Register(typeof(B))]
public partial class Container : IContainer, IContainer {}There are two ways to use a container - using the Run methods or the Resolve methods.
Either way you'll find it easier if you use the extension methods defined in StrongInject.ContainerExtensions rather than those defined directly on the container, so make sure you're using StrongInject;
The Run method on IContainer takes a Func. It resolves an instance of T, calls the func, disposes of any dependencies which require disposal, and then returns the result of the func. This ensures that you can't forget to dispose any dependencies, but you must make sure not to leak those objects out of the delegate. There are also overloads that allow you to pass in a void returning lambda.
public class Program
{
public static void Main()
{
System.Console.WriteLine(new Container().Run(x => x.ToString()));
}
}In some cases this isn't flexible enough, for example if you want to use StrongInject from another IOC container, or you need more fine grained control over the lifetime of T.
For these cases you can call the Resolve method. This returns an Owned which is essentially a disposable wrapper over T. Make sure you call Owned.Dispose once you're done using Owned.Value.
public class Program
{
public static void Main()
{
using var ownedOfA = new Container().Resolve();
var a = ownedOfA.Value;
System.Console.WriteLine(a.ToString());
}
}As you saw above, you can register a type with a container using the RegisterAttribute:
using StrongInject;
public class A {}
public class B {}
[Register(typeof(A))]
[Register(typeof(B))]
public partial class Container : IContainer, IContainer {}All the dependencies of the container type parameter must be registered or you will get a compile time error.
By default [Register(typeof(A))] will register a type A as itself. You can however register a type as any base type or implemented interface:
using StrongInject;
public class BaseBase {}
public interface IBase {}
public class Base : BaseBase, IBase {}
public interface IA {}
public class A : Base, IA {}
[Register(typeof(A), typeof(IA), typeof(IBase), typeof(BaseBase))]
public partial class Container : IContainer {}If you do so, you will have to explicitly also register it as itself if that is desired: [Register(typeof(A), typeof(A), typeof(IA), typeof(IBase), typeof(BaseBase))]
If you're on C# preview language version a type, you can use the generic versions of the attribute: [Register] and [Register].
If there is a single public non-parameterless constructor, StrongInject will use that to construct the type. If there is no public non-parameterless constructor StrongInject will use the parameterless constructor if it exists and is public. Else it will report an error.
You can register generic types as well:
public class A {}
public interface IB {}
public class B : IB {}
[Register(typeof(A<>))]
[Register(typeof(B), typeof(IB))]
public partial class Container : IContainer>, IContainer> {}The scope of a registration determines how often a new instance is created, how long it lives, and who uses it.
It can be set as the second parameter of a registration:
using StrongInject;
public class A {}
public interface IB {}
public class B : IB {}
[Register(typeof(A), Scope.SingleInstance)]
[Register(typeof(B), Scope.InstancePerResolution, typeof(IB))]
public partial class Container : IContainer, IContainer {}There are currently 3 different scopes:
Instance Per Resolution
This is the default scope.
A single instance is shared between all dependencies created for a single resolution. For example if A depends on B and C, and B and C both depend on an instance of D, then when A is resolved B and C will share the same instance of D.
Note every SingleInstance dependency defines a separate resolution, so if B and/or C are SingleInstance they would not share an instance of D.
Similarly every lambda defines a separate resolution, so if A depends on Func, then each time Func is invoked a fresh instance of both B and D will be created.
Instance Per Dependency
A new instance is created for every usage. For example even if type B appears twice in the constructor of A, two different instances will be passed into the constructor.
SingleInstance
A single instance will be shared across all dependencies, from any resolution
You can add registrations to any type, and then import them using the RegisterModuleAttribute, or by inheriting from the module. This allows you to create reusable modules of common registrations.
No open issues yet, or sync has not completed.