diff --git a/src/Jab.Unity/.gitignore b/src/Jab.Unity/.gitignore index d8e1f31..bf71615 100644 --- a/src/Jab.Unity/.gitignore +++ b/src/Jab.Unity/.gitignore @@ -1,3 +1,4 @@ *.dll !*.meta -*.tgz \ No newline at end of file +*.tgz +README.md \ No newline at end of file diff --git a/src/Jab.Unity/README.md b/src/Jab.Unity/README.md deleted file mode 100644 index db77ef0..0000000 --- a/src/Jab.Unity/README.md +++ /dev/null @@ -1,251 +0,0 @@ -# Jab Compile Time Dependency Injection - -[![Nuget (with prereleases)](https://img.shields.io/nuget/v/Jab)](https://www.nuget.org/packages/Jab) - -[![Nuget (with prereleases)](https://img.shields.io/nuget/vpre/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 SDK support, .NET Standard 2.0 support - -## 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 -- Factory registration -- Instance registration -- `IEnumerable` resolution -- `IDisposable` and `IAsyncDisposable` support -- `IServiceProvider` support - -The 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(); -``` - -### 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. - -### 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 | -``` - -## 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.