Jab Dependency Injection Container

repository·main·Indexed 22 days ago

https://github.com/pakrym/jab

Jab is a high-performance, compile-time dependency injection container for C# powered by Source Generators. Version 0.12.0 provides fast startup and resolution with AOT/linker friendliness and no runtime dependencies. It supports Singleton, Scoped, and Transient lifetimes via attributes, named services, factory-based instantiation, and reusable registration modules.

Tokens
2.8K
Snippets
11
Records
16
Agent score
29%

What's inside Jab

  1. Share registrations using Modules

    main

    Modules allow you to group service registrations into reusable sets.

    1. Create an interface and decorate it with [ServiceProviderModule].
    2. Add service registrations to the interface using attributes.
    3. Use the [Import] attribute on your main [ServiceProvider] to include the module's services.
    [ServiceProviderModule]
    [Singleton(typeof(IService), typeof(ServiceImplementation))]
    public interface IMyModule
    {
    }
    
    [ServiceProvider]
    [Import(typeof(IMyModule))]
    internal partial class MyServiceProvider
    {
    }
    
    MyServiceProvider c = new MyServiceProvider();
    IService service = c.GetService<IEnumerable<IService>>();
  2. Quickstart: Define and use a Service Provider

    main

    Jab uses a source generator to create a dependency injection container. You define a partial class decorated with [ServiceProvider] and register your services using attributes like [Transient], [Singleton], or [Scoped].

    1. Define your interfaces and implementations.
    2. Create a partial class decorated with [ServiceProvider].
    3. Register services using attributes.
    4. Instantiate the generated class and call GetService<T>().
    internal interface IService
    {
        void M();
    }
    
    internal class ServiceImplementation : IService
    {
        public void M()
        {
        }
    }
    
    [ServiceProvider]
    [Transient(typeof(IService), typeof(ServiceImplementation))]
    internal partial class MyServiceProvider { }
    
    MyServiceProvider c = new MyServiceProvider();
    IService service = c.GetService<IService>();
  3. Install Jab via NuGet

    main

    To use Jab in your C# project, add a package reference to the Jab NuGet package. It is recommended to set PrivateAssets="all" to ensure the source generator is used during compilation without adding runtime dependencies to your consumers.

    <ItemGroup>
        <PackageReference Include="Jab" Version="0.12.0" PrivateAssets="all" />
    </ItemGroup>
    <ItemGroup>
        <PackageReference Include="Jab" Version="0.12.0" PrivateAssets="all" />
    </ItemGroup>
  4. Install Jab in Unity

    main

    To use Jab in Unity, you must add it as a scoped registry in your project's manifest.json:

    1. Navigate to the Packages directory of your project.
    2. Edit manifest.json.
    3. Add https://registry.npmjs.org/ to scopedRegistries and include com.pakrym in scopes.
    4. Add com.pakrym.jab to the dependencies list.
    {
      "scopedRegistries": [
        {
          "name": "npmjs",
          "url": "https://registry.npmjs.org/",
          "scopes": [
            "com.pakrym"
          ]
        }
      ],
      "dependencies": {
        "com.pakrym.jab": "0.12.0",
        ...
      }
    }
  5. Define a Service Provider

    main

    To use Jab, you must mark a class as a service provider using the [ServiceProvider] attribute. You can optionally specify RootServices to define which types should be available at the root level of the container.

    If you are organizing your dependencies into logical groups, you can use [ServiceProviderModule] on an interface to define a module that can be imported into other providers.

  6. Register services with lifetimes

    main

    Jab uses attributes to register services with specific lifetimes. You can apply these to classes or interfaces. Most attributes support specifying the ServiceType, an optional ImplementationType, an optional Name for named resolution, and an optional Factory (as a string representing a method/property) or Instance (as a string representing a field/property).

    Lifetimes

    • [Singleton]: A single instance is created and shared throughout the application lifetime.
    • [Scoped]: An instance is created within a specific scope.
    • [Transient]: A new instance is created every time the service is requested.

    If GENERIC_ATTRIBUTES is enabled, you can use strongly-typed versions (e.g., [Singleton<TService>]) for cleaner syntax.

  7. Register services with Generic attributes

    main

    If your project targets C# 11 or greater, you can use generic attributes for more compact registration, avoiding typeof calls.

    [ServiceProvider]
    [Scoped<IService, ServiceImplementation>]
    [Import<IMyModule>]
    internal partial class MyServiceProvider { }
  8. Register services using Factories

    main

    If you need custom instantiation logic, define a method in your [ServiceProvider] class and reference it using the Factory property of the [Singleton] or [Transient] attribute.

    • For [Transient], the factory is invoked every time the service is resolved.
    • For [Singleton], the factory is invoked only once.
    • Factories support parameter injection just like constructors.
    [ServiceProvider]
    [Transient(typeof(IService), Factory = nameof(MyServiceFactory))]
    [Transient(typeof(SomeOtherService))]
    internal partial class MyServiceProvider {
        public IService MyServiceFactory(SomeOtherService other) => new ServiceImplementation(other);
    }
    
    MyServiceProvider c = new MyServiceProvider();
    IService service = c.GetService<IService>();
  9. Use Scoped services

    main

    Scoped services are created once per service provider scope. Use the CreateScope() method to create a scope. When the scope is disposed, all IDisposable and IAsyncDisposable services resolved from that scope are also disposed.

    [ServiceProvider]
    [Scoped(typeof(IService), typeof(ServiceImplementation))]
    internal partial class MyServiceProvider { }
    
    MyServiceProvider c = new MyServiceProvider();
    using MyServiceProvider.Scope scope = c.CreateScope();
    IService service = scope.GetService<IService>();
  10. Use Named services

    main

    You can assign names to service registrations using the Name property in the registration attribute. To resolve a specific named service, use the [FromNamedServices("...")] attribute on the constructor parameter. Jab also supports the standard [FromKeyedServices] attribute from Microsoft.Extensions.DependencyInjection.

    [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)
        {}
    }
  11. Register Singleton Instances

    main

    To use an existing object as a service, define a property in your [ServiceProvider] class and use the Instance property of the [Singleton] attribute to point to that property name. You must then initialize the property on the provider instance before resolving the service.

    [ServiceProvider]
    [Singleton(typeof(IService), Instance = nameof(MyServiceInstance))]
    internal partial class MyServiceProvider {
        public IService MyServiceInstance { get;set; }
    }
    
    MyServiceProvider c = new MyServiceProvider();
    c.MyServiceInstance = new ServiceImplementation();
    
    IService service = c.GetService<IService>();
  12. Configure Root services for IEnumerable resolution

    main

    By default, IEnumerable<T> accessors are only generated if they are requested by other service constructors. To ensure a root IEnumerable<T> accessor is generated and available via GetService<IEnumerable<T>>(), use the RootServices property of the [ServiceProvider] attribute.

    [ServiceProvider(RootServices = new [] {typeof(IEnumerable<IService>)} 
    [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<IEnumerable<IService>>();