Injector.NET
0.0.1-a
See the version list below for details.
dotnet add package Injector.NET --version 0.0.1-a
NuGet\Install-Package Injector.NET -Version 0.0.1-a
<PackageReference Include="Injector.NET" Version="0.0.1-a" />
paket add Injector.NET --version 0.0.1-a
#r "nuget: Injector.NET, 0.0.1-a"
// Install Injector.NET as a Cake Addin #addin nuget:?package=Injector.NET&version=0.0.1-a&prerelease // Install Injector.NET as a Cake Tool #tool nuget:?package=Injector.NET&version=0.0.1-a&prerelease
Injector.NET
A featherweight dependency injector written in C#.
Overview
Dependency Injection is a design pattern that helps you separate the dependencies of your code from its behavior. Additionaly it makes the code easy to test by let you mock the dependencies in your unit tests. This library provides several mechanisms to register, acquire and inherit services. Let's see how!
About services in general
- They are interfaces (including the injector itself).
- They are hosted in an injector.
- Every service can be requested multiple times but can be registered only once.
- Services are instantiated only when they are requested.
- Every service has its own lifetime, which can be:
Singleton
: Instantiated only once on the first request and released automatically when the containing injector is released.Transient
: Instantiated on every request and the caller is responsible for freeing the requested services.
Injector lifetime
The lifetime of an injector is the following:
- Instantiating the injector
- Registering new services
- Updating existing services
- Locking the injector
- Requesting services (even parallelly)
- Destroying the injector (either arbitrarily or automatically), see below
Instantiating the injector
Before we'd start we need to instantiate the root injector first:
using(IInjector injector = Injector.Create()){...}
Why root? Because it has no parent injector which means we are responsible for freeing (calling the Dispose()
on) it. Child injectors (created by injector.CreateChild()
call) can also be disposed arbitrarily, but it's not mandatory because the parent takes care of it:
using(IInjector injector = Injector.Create())
{
IInjector child_1 = injector.CreateChild();
Assert.That(child_1.Parent == injector);
.
.
using(IInjector child_2 = injector.CreateChild())
{
Assert.That(child_2.Parent == injector);
.
.
} // child_2 released here
} // child_1 released here
Remarks:
- Child injectors inherit their parent's services, but modifying them does not affect their parents.
- Every application should have only one root injector.
Registering new services
Registering a service can be done via several patterns (I name them recipes):
- Service recipe: This is the most common way to file a service.
To register a simple service just call the
Service()
generic method with the desired interface, implementation and lifetime:
You can register generic services as well:injector.Service<IMyService, MyService>(Lifetime.Transient);
Later you may request the specialized version of the service without registering it:injector.Service(typeof(IMyGenericService<>), typeof(MyGenericService<>), Lifetime.Singleton);
Remarks:injector.Get<IMyGenericService<string>>();
- Implementations must not have more than one public constructor!
- A service can request other services via the constructor parameters:
public MyService(IInjector injector, IService_1 dep1, IService_2 dep2){...}
- Instatiating is done on the request (Get() call).
- Lazy recipe: Similar to the service recipe except that the implementation is unknown in build time (e.g. the service implementation is placed in a module we want to load only if the implementation is requested). So instead of passing the implementation we provide a resolver which will resolve the implementation (NOT a service instance) on the first request:
Note that we can also register generic services via this method.injector.Lazy<IMyService>(new MyServiceResolver(), Lifetime.Transient);
- Factory recipe: As the name suggests services registered by this way have a factory function (that is called on the first request):
It can be useful e.g. if the service has more than one public constructor. In addition we can also register generic services, in this case the factory function will be called with the specialized interface:injector.Factory<IMyService>(thisInjector => new MyService(), Lifetime.Singleton);
injector.Factory(typeof(IMyGenericService<>), (thisInjector, specializedIMyGenericService) => MyActivator.CreateInstance(typeof(MyGenericService<>), specializedIMyGenericService.GetGenericArguments()));
- Instance recipe: An instances is a "predefined value" that can act as a service:
The second parameter instructs the injector to dispose the instance when the injector itself is disposed.injector.Instance<IMyInstance>(myInstance, true);
Note that you should not register the injector itself it is done by the system automatically.
Updating existing services
In practice, it's useful to separate common functionality (e.g. parameter validation) from the implementation. In this library this can be achieved by proxy pattern. In a brief example:
injector
.Service<IMyModule, MyModule>()
.Proxy<IMyModule>((thisInjector, myModuleInstance) => new ParameterValidatorProxy<IMyModule>(myModuleInstance).Proxy);
Where the ParameterValidatorProxy<TInterface>
is an InterfaceProxy<TInterface> containing the parameter validation logic. And that's all. Proxy pattern can be applied against every non-generic service, in any number.
Note that:
- Trying to proxy a generic service or an instance (registered via
Instance()
call) will throw an exception. - Proxying an inherited service WILL NOT affect the parent.
Locking the injector
To prevent accidental modification of the injector, after configuring you can lock it (by call the prameterless Lock()
method). After locking any attempt to modify the state of the injector will cause an exception.
It's safe to call the following functions on a locked injector:
Get()
CreateChild()
Dispose()
Note thatLock()
affects only the instance on which it was called.
Requesting services
After finishing the configuration you can request services via the Get()
method:
IMyService svc = injector.Get<IMyService>();
Keep in mind the followings:
- Calling
Get()
is a thread safe operation so you may share the configured injector between different threads. - Requesting an unregistered or an open generic service will cause an exception.
- Requesting services as a constructor parameter is more convenient than using the
Get()
method. - The caller should free the requested service if it is an
IDisposable
and has theTransient
lifetime. To determine it's lifetime call theQueryServiceInfo()
method.
API Docs
You can find the detailed API docs here.
Supported frameworks
This project currently targets .NET Core 2.X only.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 is compatible. netcoreapp2.1 is compatible. netcoreapp2.2 is compatible. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
This package has no dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Injector.NET:
Package | Downloads |
---|---|
RPC.NET.Server
SDK designed for building lightweight RPC servers. |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
10.1.0 | 378 | 12/28/2023 |
10.0.0 | 152 | 12/15/2023 |
9.0.0 | 181 | 6/1/2023 |
8.0.1 | 237 | 3/12/2023 |
8.0.0 | 248 | 2/25/2023 |
7.0.0 | 280 | 2/19/2023 |
7.0.0-preview5 | 140 | 1/22/2023 |
7.0.0-preview4 | 118 | 11/20/2022 |
7.0.0-preview3 | 171 | 7/24/2022 |
7.0.0-preview2 | 163 | 6/25/2022 |
7.0.0-preview1 | 148 | 4/23/2022 |
6.2.1 | 480 | 3/17/2022 |
6.2.0 | 437 | 3/15/2022 |
6.1.0 | 623 | 1/28/2022 |
6.0.0 | 956 | 12/12/2021 |
6.0.0-preview2 | 521 | 10/11/2021 |
6.0.0-preview1 | 253 | 10/9/2021 |
5.0.1 | 772 | 7/1/2021 |
5.0.0 | 384 | 6/20/2021 |
4.0.1 | 1,252 | 3/19/2021 |
4.0.0 | 399 | 3/13/2021 |
4.0.0-preview8 | 426 | 3/4/2021 |
4.0.0-preview7 | 326 | 3/2/2021 |
4.0.0-preview6 | 223 | 3/1/2021 |
4.0.0-preview5 | 378 | 2/20/2021 |
4.0.0-preview4 | 271 | 11/17/2020 |
4.0.0-preview3 | 333 | 10/7/2020 |
4.0.0-preview2 | 326 | 9/22/2020 |
4.0.0-preview1 | 308 | 8/28/2020 |
3.4.1 | 1,167 | 8/26/2020 |
3.4.0 | 605 | 8/18/2020 |
3.3.1 | 1,476 | 7/9/2020 |
3.3.0 | 507 | 6/25/2020 |
3.2.0 | 481 | 6/15/2020 |
3.1.0 | 511 | 6/3/2020 |
3.0.1 | 522 | 5/25/2020 |
3.0.0 | 517 | 5/25/2020 |
3.0.0-preview2 | 333 | 5/22/2020 |
3.0.0-preview1 | 349 | 5/22/2020 |
2.1.0 | 489 | 5/5/2020 |
2.0.1 | 472 | 4/28/2020 |
2.0.0 | 483 | 4/17/2020 |
2.0.0-preview4 | 342 | 4/14/2020 |
2.0.0-preview3 | 329 | 3/31/2020 |
2.0.0-preview2 | 368 | 3/27/2020 |
2.0.0-preview1 | 352 | 3/20/2020 |
1.3.2 | 495 | 3/6/2020 |
1.3.1 | 466 | 3/3/2020 |
1.3.0 | 483 | 2/27/2020 |
1.3.0-preview2 | 345 | 2/21/2020 |
1.3.0-preview1 | 344 | 2/14/2020 |
1.2.0 | 449 | 2/9/2020 |
1.2.0-preview1 | 357 | 2/7/2020 |
1.1.1 | 573 | 1/27/2020 |
1.1.0 | 541 | 1/27/2020 |
1.0.0 | 499 | 1/18/2020 |
1.0.0-preview7 | 368 | 1/15/2020 |
1.0.0-preview6 | 366 | 12/12/2019 |
1.0.0-preview5 | 346 | 11/27/2019 |
1.0.0-preview4 | 355 | 11/7/2019 |
1.0.0-preview3 | 350 | 10/20/2019 |
1.0.0-preview2 | 383 | 10/10/2019 |
1.0.0-preview1 | 374 | 10/9/2019 |
0.0.3 | 579 | 6/21/2019 |
0.0.2 | 523 | 6/15/2019 |
0.0.1 | 610 | 6/6/2019 |
0.0.1-a | 452 | 6/6/2019 |
Initial version.