diff --git a/.gitea/workflows/nuget-pkg-build.yml b/.gitea/workflows/nuget-pkg-build.yml
index 2d56109..6105cb5 100644
--- a/.gitea/workflows/nuget-pkg-build.yml
+++ b/.gitea/workflows/nuget-pkg-build.yml
@@ -21,6 +21,6 @@ jobs:
#- name: Test
# run: dotnet test -c Release --no-build
- 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
run: dotnet nuget push "*.nupkg" --api-key ${{secrets.NUGETAPIKEY}} --source https://api.nuget.org/v3/index.json
diff --git a/DISandbox/Program.cs b/DISandbox/Program.cs
index 9342eea..0ad32fc 100644
--- a/DISandbox/Program.cs
+++ b/DISandbox/Program.cs
@@ -21,8 +21,23 @@ interface IOtherService {
class GuidService : IOtherService {
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) {
logService.Log($"[GuidDependantService] {message} (ID: {guidService.Id})");
}
diff --git a/README.md b/README.md
index d468327..a1f0b4b 100644
--- a/README.md
+++ b/README.md
@@ -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.
- **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.
+- **Greedy matching constructor selection**: When resolving a service, the constructor with the most parameters that can be satisfied by the container is chosen.
## Limitations
- **No support for scoped lifetimes**
- **No support for property injection or method injection.**
-- **Every service should have only 1 constructor.** (planned to be lifted in future versions)
## Usage
````csharp
diff --git a/Syrette/ServiceContainer.cs b/Syrette/ServiceContainer.cs
index eca2483..f499d6d 100644
--- a/Syrette/ServiceContainer.cs
+++ b/Syrette/ServiceContainer.cs
@@ -1,4 +1,6 @@
-namespace Syrette;
+using System.Reflection;
+
+namespace Syrette;
///
/// Container for managing service registrations and resolutions.
@@ -26,9 +28,7 @@ public class ServiceContainer {
descriptors.Add(new ServiceDescriptor {
ServiceType = typeof(TInterface),
ImplementationType = typeof(TImplementation),
- Lifetime = ServiceLifetime.Lifetime,
- RequiredTypes = typeof(TImplementation).GetConstructors().Single()
- .GetParameters().Select(p => p.ParameterType).ToList()
+ Lifetime = ServiceLifetime.Lifetime
});
return this;
}
@@ -43,9 +43,7 @@ public class ServiceContainer {
descriptors.Add(new ServiceDescriptor {
ServiceType = typeof(TInterface),
ImplementationType = typeof(TImplementation),
- Lifetime = ServiceLifetime.Transient,
- RequiredTypes = typeof(TImplementation).GetConstructors().Single()
- .GetParameters().Select(p => p.ParameterType).ToList()
+ Lifetime = ServiceLifetime.Transient
});
return this;
}
@@ -71,20 +69,27 @@ public class ServiceContainer {
if (descriptor == null) throw new Exception($"Service of type {typeof(TInterface)} not registered.");
- // Ensure all required dependencies are registered
- //TODO: some services might be asking for specific implementations, not interfaces. We should check for that too.
- var missing = descriptor.RequiredTypes
- //filter all required types that are not in the registered descriptors
- .Where(t => descriptors.All(d => d.ServiceType != t))
- .Select(t => t.Name)
- .ToList();
+ var ctors = descriptor.ImplementationType.GetConstructors();
+ int max = -1;
+ ConstructorInfo? bestCtor = null;
- if (missing.Any())
- throw new Exception($"Cannot create service of type {typeof(TInterface)}. Missing dependencies: {string.Join(", ", missing)}");
+ foreach (var ctor in ctors) {
+ 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
if (descriptor.Lifetime != ServiceLifetime.Lifetime) {
- var service = Instantiate(descriptor);
+ var service = Instantiate(descriptor, bestCtor);
return service;
}
@@ -97,12 +102,22 @@ public class ServiceContainer {
return newSingleton;
}
- private TInterface Instantiate(ServiceDescriptor descriptor) {
- var par = descriptor.ImplementationType
- .GetConstructors().Single()
- .GetParameters()
- .Select(p => p.ParameterType)
- .ToList();
+ private TInterface Instantiate(ServiceDescriptor descriptor, ConstructorInfo? ctor = null) {
+ if (ctor == null && descriptor.ImplementationType.GetConstructors().Length > 1)
+ throw new Exception($"Multiple constructors found for type {descriptor.ImplementationType}. Please provide a specific constructor.");
+
+ List par;
+
+ 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];
diff --git a/Syrette/ServiceDescriptor.cs b/Syrette/ServiceDescriptor.cs
index 587efb6..1b9eec2 100644
--- a/Syrette/ServiceDescriptor.cs
+++ b/Syrette/ServiceDescriptor.cs
@@ -19,9 +19,4 @@ public class ServiceDescriptor
/// Gets or sets the lifetime of the service (e.g., Singleton or Transient).
///
public required ServiceLifetime Lifetime { get; set; }
-
- ///
- /// Gets or sets the list of types required by the implementation (dependencies).
- ///
- public List RequiredTypes { get; set; } = new();
}
\ No newline at end of file
diff --git a/Syrette/Syrette.csproj b/Syrette/Syrette.csproj
index 0f543d0..697dc1f 100644
--- a/Syrette/Syrette.csproj
+++ b/Syrette/Syrette.csproj
@@ -6,11 +6,10 @@
- net9.0
latest
enable
enable
- 0.0.1.1-alpha
+ 0.0.1.2-alpha
Syrette
Lorefice Samuele
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.
@@ -24,6 +23,7 @@
Samuele Lorefice
true
true
+ net9.0;net8.0
true