Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/pakrym/jab
C# Source Generator based dependency injection container implementation.
https://github.com/pakrym/jab
dependency-injection microsoft-extensions roslyn roslyn-generator singleton-service source-generators
Last synced: 4 days ago
JSON representation
C# Source Generator based dependency injection container implementation.
- Host: GitHub
- URL: https://github.com/pakrym/jab
- Owner: pakrym
- License: mit
- Created: 2020-12-11T22:14:59.000Z (about 4 years ago)
- Default Branch: main
- Last Pushed: 2024-01-16T07:24:40.000Z (11 months ago)
- Last Synced: 2024-11-21T06:23:04.319Z (21 days ago)
- Topics: dependency-injection, microsoft-extensions, roslyn, roslyn-generator, singleton-service, source-generators
- Language: C#
- Homepage:
- Size: 479 KB
- Stars: 1,044
- Watchers: 24
- Forks: 35
- Open Issues: 20
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- RSCG_Examples - https://github.com/pakrym/jab
- csharp-source-generators - Jab - ![stars](https://img.shields.io/github/stars/pakrym/jab?style=flat-square&cacheSeconds=604800) ![last commit](https://img.shields.io/github/last-commit/pakrym/jab?style=flat-square&cacheSeconds=86400) - Compile Time Dependency Injection (Source Generators / Dependency Injection (IoC Container))
README
# Jab Compile Time Dependency Injection
[![Nuget](https://img.shields.io/nuget/v/Jab)](https://www.nuget.org/packages/Jab)
Jab provides a [C# Source Generator](https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/) based dependency injection container implementation.
- Fast startup (200x faster than Microsoft.Extensions.DependencyInjection). [Details](#Startup-Time).
- Fast resolution (7x faster than Microsoft.Extensions.DependencyInjection). [Details](#GetService).
- No runtime dependencies.
- AOT and linker friendly, all code is generated during project compilation.
- Clean stack traces:
![stacktrace](https://raw.githubusercontent.com/pakrym/jab/main/doc/stacktrace.png)
- Readable generated code:
![generated code](https://raw.githubusercontent.com/pakrym/jab/main/doc/generatedcode.png)
- Registration validation. Container configuration issues become compiler errors:
![generated code](https://raw.githubusercontent.com/pakrym/jab/main/doc/errors.png)
- Incremental generation, .NET 5/6/7/8 SDK support, .NET Standard 2.0 support, [Unity support](README.md#Unity-installation)## Example
Add Jab package reference:
```xml
```
Define a service and implementation:
``` C#
internal interface IService
{
void M();
}internal class ServiceImplementation : IService
{
public void M()
{
}
}
```Define a composition root and register services:
```C#
[ServiceProvider]
[Transient(typeof(IService), typeof(ServiceImplementation))]
internal partial class MyServiceProvider { }
```Use the service provider:
``` C#
MyServiceProvider c = new MyServiceProvider();
IService service = c.GetService();
```## Features
- No runtime dependency, safe to use in libraries
- Transient, Singleton, Scoped service registration
- Named registrations
- Factory registration
- Instance registration
- `IEnumerable` resolution
- `IDisposable` and `IAsyncDisposable` support
- `IServiceProvider` supportThe plan is to support the minimum feature set Microsoft.Extensions.DependencyInjection.Abstraction requires but *NOT* the `IServiceCollection`-based registration syntax as it is runtime based.
### Singleton services
Singleton services are created once per container lifetime in a thread-safe manner and cached.
To register a singleton service use the `SingletonAttribute`:```C#
[ServiceProvider]
[Singleton(typeof(IService), typeof(ServiceImplementation))]
internal partial class MyServiceProvider { }
```### Singleton Instances
If you want to use an existing object as a service define a property in the container declaration and use the `Instance` property of the `SingletonAttribute` to register the service:
```C#
[ServiceProvider]
[Singleton(typeof(IService), Instance = nameof(MyServiceInstance))]
internal partial class MyServiceProvider {
public IService MyServiceInstance { get;set; }
}
```Then initialize the property during the container creation:
```C#
MyServiceProvider c = new MyServiceProvider();
c.MyServiceInstance = new ServiceImplementation();IService service = c.GetService();
```### Named services
Use the `Name` property to assign a name to your service registrations and `[FromNamedServices("...")]` attribute to resolve a service using its name.
```C#
[ServiceProvider]
[Singleton(typeof(INotificationService), typeof(EmailNotificationService), Name="email")]
[Singleton(typeof(INotificationService), typeof(SmsNotificationService), Name="sms")]
[Singleton(typeof(Notifier))]
internal partial class MyServiceProvider {}class Notifier
{
public Notifier(
[FromNamedServices("email")] INotificationService email,
[FromNamedServices("sms")] INotificationService sms)
{}
}
```NOTE: Jab also recognizes the `[FromKeyedServices]` attribute from `Microsoft.Extensions.DependencyInjection`.
### Factories
Sometimes it's useful to provide a custom way to create a service instance without using the automatic construction selection.
To do this define a method in the container declaration and use the `Factory` property of the `SingletonAttribute` or `TransientAttribute` to register the service:```C#
[ServiceProvider]
[Transient(typeof(IService), Factory = nameof(MyServiceFactory))]
internal partial class MyServiceProvider {
public IService MyServiceFactory() => new ServiceImplementation();
}MyServiceProvider c = new MyServiceProvider();
IService service = c.GetService();
```When using with `TransientAttribute` the factory method would be invoked for every service resolution.
When used with `SingletonAttribute` it would only be invoked the first time the service is requested.Similar to constructors, factories support parameter injection:
```
[ServiceProvider]
[Transient(typeof(IService), Factory = nameof(MyServiceFactory))]
[Transient(typeof(SomeOtherService))]
internal partial class MyServiceProvider {
public IService MyServiceFactory(SomeOtherService other) => new ServiceImplementation(other);
}
```### Scoped Services
Scoped services are created once per service provider scope. To create a scope use the `CreateScope()` method of the service provider.
Service are resolved from the scope using the `GetService()` call.```C#
[ServiceProvider]
[Scoped(typeof(IService), typeof(ServiceImplementation))]
internal partial class MyServiceProvider { }MyServiceProvider c = new MyServiceProvider();
using MyServiceProvider.Scope scope = c.CreateScope();
IService service = scope.GetService();
```When the scope is disposed all `IDisposable` and `IAsyncDisposable` services that were resolved from it are disposed as well.
### Generic registration attributes
You can use generic attributes to register services if your project targets `net7.0` or `net6.0` and has `LangVersion` set to preview.
```xml
net7.0
```
Generic attributes allow declaration to be more compact by avoiding the `typeof` calls:
``` C#
[ServiceProvider]
[Scoped]
[Import]
internal partial class MyServiceProvider { }
```### Modules
Often, a set of service registrations would represent a distinct set of functionality that can be included into arbitrary
service provider. Modules are used to implement registration sharing. To define a module create an interface and mark it with `ServiceProviderModuleAttribute`. Service registrations can be listed in module the same way they are in the service provider.```C#
[ServiceProviderModule]
[Singleton(typeof(IService), typeof(ServiceImplementation))]
public interface IMyModule
{
}
```To use the module apply the `Import` attribute to the service provider type:
```C#
[ServiceProvider]
[Import(typeof(IMyModule))]
internal partial class MyServiceProvider
{
}MyServiceProvider c = new MyServiceProvider();
IService service = c.GetService>();
```Modules can import other modules as well.
**NOTE**: module service and implementation types have to be accessible from the project where service provider is generated.
## Root services
By default, `IEnumerable<...>` service accessors are only generated when requested by other service constructors. If you would like to have a root `IEnumerable<..>` accessor generated use the `RootService` parameter of the `ServiceProvider` attribute. The generator also scans all the `GetService<...>` usages and tries to all collected type arguments as the root service.
``` C#
[ServiceProvider(RootServices = new [] {typeof(IEnumerable)})]
[Singleton(typeof(IService), typeof(ServiceImplementation))]
[Singleton(typeof(IService), typeof(ServiceImplementation))]
[Singleton(typeof(IService), typeof(ServiceImplementation))]
internal partial class MyServiceProvider
{
}MyServiceProvider c = new MyServiceProvider();
IService service = c.GetService>();
```## Samples
### Console application
Sample Jab usage in console application can be found in [src/samples/ConsoleSample](src/samples/ConsoleSample)
## Performance
The performance benchmark project is available in [src/Jab.Performance/](src/Jab.Performance/).
### Startup time
The startup time benchmark measures time between application startup and the first service being resolved.
```
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |------------:|----------:|----------:|-------:|--------:|-------:|-------:|------:|----------:|
| MEDI | 2,437.88 ns | 14.565 ns | 12.163 ns | 220.91 | 2.72 | 0.6332 | 0.0114 | - | 6632 B |
| Jab | 11.03 ns | 0.158 ns | 0.123 ns | 1.00 | 0.00 | 0.0046 | - | - | 48 B |
```### GetService
The `GetService` benchmark measures the `provider.GetService()` call.
```
| Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------- |----------:|----------:|----------:|------:|--------:|-------:|------:|------:|----------:|
| MEDI | 39.340 ns | 0.2419 ns | 0.2263 ns | 7.01 | 0.09 | 0.0023 | - | - | 24 B |
| Jab | 5.619 ns | 0.0770 ns | 0.0643 ns | 1.00 | 0.00 | 0.0023 | - | - | 24 B |
```## Unity installation
1. Navigate to the Packages directory of your project.
2. Adjust the [project manifest file](https://docs.unity3d.com/Manual/upm-manifestPrj.html) manifest.json in a text editor.
3. Ensure `https://registry.npmjs.org/` is part of `scopedRegistries`.
4. Ensure `com.pakrym` is part of `scopes`.
5. Add `com.pakrym.jab` to the dependencies, stating the latest version.A minimal example ends up looking like this:
```
{
"scopedRegistries": [
{
"name": "npmjs",
"url": "https://registry.npmjs.org/",
"scopes": [
"com.pakrym"
]
}
],
"dependencies": {
"com.pakrym.jab": "0.10.2",
...
}
}
```## Debugging locally
Run `dotnet build /t:CreateLaunchSettings` in the `Jab.Tests` directory would update the `Jab\Properties\launchSettings.json` file to include `csc` invocation that allows F5 debugging of the generator targeting the `Jab.Tests` project.