Added support for multiple constructors and best match greedy resolution.
Some checks failed
Nuget Pkg Build / build (push) Failing after 25s
Some checks failed
Nuget Pkg Build / build (push) Failing after 25s
This commit is contained in:
@@ -21,6 +21,6 @@ jobs:
|
|||||||
#- name: Test
|
#- name: Test
|
||||||
# run: dotnet test -c Release --no-build
|
# run: dotnet test -c Release --no-build
|
||||||
- name: Pack nugets
|
- name: Pack nugets
|
||||||
run: dotnet pack Syrette -c Release --no-build --output .
|
run: dotnet pack Syrette -c Release --no-build --output . --include-symbols --include-source
|
||||||
- name: Push to NuGet
|
- name: Push to NuGet
|
||||||
run: dotnet nuget push "*.nupkg" --api-key ${{secrets.NUGETAPIKEY}} --source https://api.nuget.org/v3/index.json
|
run: dotnet nuget push "*.nupkg" --api-key ${{secrets.NUGETAPIKEY}} --source https://api.nuget.org/v3/index.json
|
||||||
|
|||||||
@@ -21,8 +21,23 @@ interface IOtherService {
|
|||||||
class GuidService : IOtherService {
|
class GuidService : IOtherService {
|
||||||
public Guid Id { get; } = Guid.NewGuid();
|
public Guid Id { get; } = Guid.NewGuid();
|
||||||
}
|
}
|
||||||
|
public interface INotRegisteredService {
|
||||||
|
void DoSomething();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GuidDependantService {
|
||||||
|
private readonly IService logService;
|
||||||
|
private readonly IOtherService? guidService;
|
||||||
|
|
||||||
|
public GuidDependantService(IService logService, INotRegisteredService guidService) {
|
||||||
|
this.logService = logService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GuidDependantService(IService logService, IOtherService guidService) {
|
||||||
|
this.logService = logService;
|
||||||
|
this.guidService = guidService;
|
||||||
|
}
|
||||||
|
|
||||||
class GuidDependantService(IService logService, IOtherService guidService) {
|
|
||||||
public void LogWithId(string message) {
|
public void LogWithId(string message) {
|
||||||
logService.Log($"[GuidDependantService] {message} (ID: {guidService.Id})");
|
logService.Log($"[GuidDependantService] {message} (ID: {guidService.Id})");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ It is designed to be used in minimalistic applications, such as console applicat
|
|||||||
- **Lightweight**: Minimal codebase with no external dependencies. 1 class, the service container. You don't need anything else.
|
- **Lightweight**: Minimal codebase with no external dependencies. 1 class, the service container. You don't need anything else.
|
||||||
- **Simple API**: You register services using two methods (one for singletons, one for transients) and resolve them with one.
|
- **Simple API**: You register services using two methods (one for singletons, one for transients) and resolve them with one.
|
||||||
- **Supports Singleton and Transient lifetimes**: Choose between singleton (one instance per container) and transient (new instance per resolution) lifetimes for your services.
|
- **Supports Singleton and Transient lifetimes**: Choose between singleton (one instance per container) and transient (new instance per resolution) lifetimes for your services.
|
||||||
|
- **Greedy matching constructor selection**: When resolving a service, the constructor with the most parameters that can be satisfied by the container is chosen.
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
- **No support for scoped lifetimes**
|
- **No support for scoped lifetimes**
|
||||||
- **No support for property injection or method injection.**
|
- **No support for property injection or method injection.**
|
||||||
- **Every service should have only 1 constructor.** (planned to be lifted in future versions)
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
````csharp
|
````csharp
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace Syrette;
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Syrette;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Container for managing service registrations and resolutions.
|
/// Container for managing service registrations and resolutions.
|
||||||
@@ -26,9 +28,7 @@ public class ServiceContainer {
|
|||||||
descriptors.Add(new ServiceDescriptor {
|
descriptors.Add(new ServiceDescriptor {
|
||||||
ServiceType = typeof(TInterface),
|
ServiceType = typeof(TInterface),
|
||||||
ImplementationType = typeof(TImplementation),
|
ImplementationType = typeof(TImplementation),
|
||||||
Lifetime = ServiceLifetime.Lifetime,
|
Lifetime = ServiceLifetime.Lifetime
|
||||||
RequiredTypes = typeof(TImplementation).GetConstructors().Single()
|
|
||||||
.GetParameters().Select(p => p.ParameterType).ToList()
|
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -43,9 +43,7 @@ public class ServiceContainer {
|
|||||||
descriptors.Add(new ServiceDescriptor {
|
descriptors.Add(new ServiceDescriptor {
|
||||||
ServiceType = typeof(TInterface),
|
ServiceType = typeof(TInterface),
|
||||||
ImplementationType = typeof(TImplementation),
|
ImplementationType = typeof(TImplementation),
|
||||||
Lifetime = ServiceLifetime.Transient,
|
Lifetime = ServiceLifetime.Transient
|
||||||
RequiredTypes = typeof(TImplementation).GetConstructors().Single()
|
|
||||||
.GetParameters().Select(p => p.ParameterType).ToList()
|
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -71,20 +69,27 @@ public class ServiceContainer {
|
|||||||
|
|
||||||
if (descriptor == null) throw new Exception($"Service of type {typeof(TInterface)} not registered.");
|
if (descriptor == null) throw new Exception($"Service of type {typeof(TInterface)} not registered.");
|
||||||
|
|
||||||
// Ensure all required dependencies are registered
|
var ctors = descriptor.ImplementationType.GetConstructors();
|
||||||
//TODO: some services might be asking for specific implementations, not interfaces. We should check for that too.
|
int max = -1;
|
||||||
var missing = descriptor.RequiredTypes
|
ConstructorInfo? bestCtor = null;
|
||||||
//filter all required types that are not in the registered descriptors
|
|
||||||
.Where(t => descriptors.All(d => d.ServiceType != t))
|
|
||||||
.Select(t => t.Name)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (missing.Any())
|
foreach (var ctor in ctors) {
|
||||||
throw new Exception($"Cannot create service of type {typeof(TInterface)}. Missing dependencies: {string.Join(", ", missing)}");
|
var parameters = ctor.GetParameters();
|
||||||
|
//check if all parameters are registered services or optional
|
||||||
|
if (!parameters.All(p => descriptors.Any(d => d.ServiceType == p.ParameterType) || p.IsOptional)) continue;
|
||||||
|
//check if this constructor has more registered parameters than the previous best
|
||||||
|
int satisfiedParams = parameters.Count(p => descriptors.Any(d => d.ServiceType == p.ParameterType));
|
||||||
|
if (satisfiedParams > max) {
|
||||||
|
max = satisfiedParams;
|
||||||
|
bestCtor = ctor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestCtor == null)
|
||||||
|
throw new Exception($"Cannot create service of type {typeof(TInterface)}. No suitable constructor found.");
|
||||||
|
|
||||||
// Transient: create a new instance each time
|
// Transient: create a new instance each time
|
||||||
if (descriptor.Lifetime != ServiceLifetime.Lifetime) {
|
if (descriptor.Lifetime != ServiceLifetime.Lifetime) {
|
||||||
var service = Instantiate<TInterface>(descriptor);
|
var service = Instantiate<TInterface>(descriptor, bestCtor);
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,12 +102,22 @@ public class ServiceContainer {
|
|||||||
return newSingleton;
|
return newSingleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TInterface Instantiate<TInterface>(ServiceDescriptor descriptor) {
|
private TInterface Instantiate<TInterface>(ServiceDescriptor descriptor, ConstructorInfo? ctor = null) {
|
||||||
var par = descriptor.ImplementationType
|
if (ctor == null && descriptor.ImplementationType.GetConstructors().Length > 1)
|
||||||
.GetConstructors().Single()
|
throw new Exception($"Multiple constructors found for type {descriptor.ImplementationType}. Please provide a specific constructor.");
|
||||||
.GetParameters()
|
|
||||||
.Select(p => p.ParameterType)
|
List<Type> par;
|
||||||
.ToList();
|
|
||||||
|
if (ctor == null)
|
||||||
|
par = descriptor.ImplementationType
|
||||||
|
.GetConstructors().Single()
|
||||||
|
.GetParameters()
|
||||||
|
.Select(p => p.ParameterType)
|
||||||
|
.ToList();
|
||||||
|
else
|
||||||
|
par = ctor.GetParameters()
|
||||||
|
.Select(p => p.ParameterType)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
object[] parameters = new object[par.Count];
|
object[] parameters = new object[par.Count];
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,4 @@ public class ServiceDescriptor
|
|||||||
/// Gets or sets the lifetime of the service (e.g., Singleton or Transient).
|
/// Gets or sets the lifetime of the service (e.g., Singleton or Transient).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required ServiceLifetime Lifetime { get; set; }
|
public required ServiceLifetime Lifetime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of types required by the implementation (dependencies).
|
|
||||||
/// </summary>
|
|
||||||
public List<Type> RequiredTypes { get; set; } = new();
|
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,10 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.0.1.1-alpha</Version>
|
<Version>0.0.1.2-alpha</Version>
|
||||||
<Title>Syrette </Title>
|
<Title>Syrette </Title>
|
||||||
<Authors>Lorefice Samuele</Authors>
|
<Authors>Lorefice Samuele</Authors>
|
||||||
<Description>Syrette is a minimalistic dependency injection library for C#. It aims to provide a simple and efficient way to achieve dependency injections in your applications without the overhead of larger frameworks.</Description>
|
<Description>Syrette is a minimalistic dependency injection library for C#. It aims to provide a simple and efficient way to achieve dependency injections in your applications without the overhead of larger frameworks.</Description>
|
||||||
@@ -24,6 +23,7 @@
|
|||||||
<Company>Samuele Lorefice</Company>
|
<Company>Samuele Lorefice</Company>
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<TargetFrameworks>net9.0;net8.0</TargetFrameworks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
|||||||
Reference in New Issue
Block a user