fix: rewrite ServiceContainer resolution logic and add AGENTS.md
- Replace broken reflection-based GetService(Type, args) with direct ResolveService method - Fix positional argument matching (type-based FirstOrDefault) - Fix greedy constructor scoring (no double-count) - Narrow TryGetService exception handling to InvalidOperationException - Throw on duplicate registrations - Add IDisposable support - Add thread-safe singleton creation - Create AGENTS.md with repo guide
This commit is contained in:
38
AGENTS.md
Normal file
38
AGENTS.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Syrette — agent guide
|
||||||
|
|
||||||
|
Minimal C# DI library targeting net8.0;net9.0;net10.0.
|
||||||
|
|
||||||
|
## Build commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet build Syrette # library only
|
||||||
|
dotnet build # everything (includes DISandbox console app)
|
||||||
|
dotnet pack Syrette -c Release --output .
|
||||||
|
```
|
||||||
|
|
||||||
|
SDK: .NET 10.0+ (see `global.json` — `rollForward: latestMinor`, `allowPrerelease: true`).
|
||||||
|
|
||||||
|
## Project structure
|
||||||
|
|
||||||
|
| Path | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `Syrette/` | Library (class lib). Entrypoints: `ServiceContainer.cs`, `ServiceDescriptor.cs`, `ServiceLifetime.cs` |
|
||||||
|
| `DISandbox/` | Unofficial manual-test sandbox. Console app referencing `Syrette`. Not part of the shipped package. |
|
||||||
|
|
||||||
|
No test project exists. CI has the test step commented out (`nuget-pkg-build.yml:24`).
|
||||||
|
|
||||||
|
## NuGet publishing
|
||||||
|
|
||||||
|
- CI (Gitea Actions) triggers on `v*` tags or `workflow_dispatch`.
|
||||||
|
- Pushes to both nuget.org and a Gitea package feed.
|
||||||
|
- Version is `0.0.1.8-alpha` per `Syrette.csproj`.
|
||||||
|
|
||||||
|
## Library API
|
||||||
|
|
||||||
|
- `ServiceContainer` exposes only `AddSingleton` / `AddTransient` (multiple overloads) and `GetService<T>()`, `GetService(Type, args)`, `TryGetService(Type, args)`, `GetServices<T>()`, `GetServiceTypes<T>()`.
|
||||||
|
- All registration methods return `ServiceContainer` for fluent chaining.
|
||||||
|
- Constructor selection is **greedy**: picks the constructor with the most parameters satisfiable by registered services or explicit args.
|
||||||
|
- Only Singleton and Transient lifetimes; no scoped, no property/method injection.
|
||||||
|
- `ServiceContainer : IDisposable` — disposes singleton instances that implement `IDisposable`.
|
||||||
|
- Duplicate registrations (same `(ServiceType, ImplementationType)`) throw `InvalidOperationException`.
|
||||||
|
- Resolution-time args (`GetService(Type, args)`) override registration-time args of the same type (type-based merge).
|
||||||
@@ -2,290 +2,227 @@
|
|||||||
|
|
||||||
namespace Syrette;
|
namespace Syrette;
|
||||||
|
|
||||||
/// <summary>
|
public class ServiceContainer : IDisposable {
|
||||||
/// Container for managing service registrations and resolutions.
|
|
||||||
/// </summary>
|
|
||||||
public class ServiceContainer {
|
|
||||||
private readonly List<ServiceDescriptor> descriptors = new();
|
private readonly List<ServiceDescriptor> descriptors = new();
|
||||||
private readonly Dictionary<Type, object> singletons = new();
|
private readonly Dictionary<Type, object> singletons = new();
|
||||||
|
private readonly object singletonLock = new();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get all registered implementation types for a given service type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TServices"></typeparam>
|
|
||||||
/// <returns></returns>
|
|
||||||
public List<Type> GetServiceTypes<TServices>() =>
|
public List<Type> GetServiceTypes<TServices>() =>
|
||||||
descriptors.Where(d => d.ServiceType == typeof(TServices))
|
descriptors.Where(d => d.ServiceType == typeof(TServices))
|
||||||
.Select(d => d.ImplementationType).ToList();
|
.Select(d => d.ImplementationType).ToList();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get all registered services for a given service type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TService"></typeparam>
|
|
||||||
public List<TService> GetServices<TService>() where TService : class =>
|
public List<TService> GetServices<TService>() where TService : class =>
|
||||||
descriptors.Where(d => d.ServiceType == typeof(TService))
|
descriptors.Where(d => d.ServiceType == typeof(TService))
|
||||||
.Select(d => (TService)GetService(d.ImplementationType, d.Arguments?.ToArray())).ToList();
|
.Select(d => (TService)ResolveService(d.ImplementationType, null)).ToList();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a singleton service with its implementation.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TInterface">Interface the service is implementing</typeparam>
|
|
||||||
/// <typeparam name="TImplementation">Implementation type of the service</typeparam>
|
|
||||||
public ServiceContainer AddSingleton<TInterface, TImplementation>()
|
public ServiceContainer AddSingleton<TInterface, TImplementation>()
|
||||||
where TInterface : class
|
where TInterface : class
|
||||||
where TImplementation : class, TInterface {
|
where TImplementation : class, TInterface {
|
||||||
descriptors.Add(new() {
|
AddDescriptor(typeof(TInterface), typeof(TImplementation), ServiceLifetime.Singleton, null);
|
||||||
ServiceType = typeof(TInterface),
|
|
||||||
ImplementationType = typeof(TImplementation),
|
|
||||||
Lifetime = ServiceLifetime.Singleton
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a singleton service with its implementation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">Arguments to be passed to the constructor, in order of appearance if of the same type.</param>
|
|
||||||
/// <typeparam name="TInterface">Interface the service is implementing</typeparam>
|
|
||||||
/// <typeparam name="TImplementation">Implementation type of the service</typeparam>
|
|
||||||
public ServiceContainer AddSingleton<TInterface, TImplementation>(params object[] args)
|
public ServiceContainer AddSingleton<TInterface, TImplementation>(params object[] args)
|
||||||
where TInterface : class
|
where TInterface : class
|
||||||
where TImplementation : class, TInterface {
|
where TImplementation : class, TInterface {
|
||||||
descriptors.Add(new() {
|
AddDescriptor(typeof(TInterface), typeof(TImplementation), ServiceLifetime.Singleton, args);
|
||||||
ServiceType = typeof(TInterface),
|
|
||||||
ImplementationType = typeof(TImplementation),
|
|
||||||
Lifetime = ServiceLifetime.Singleton,
|
|
||||||
Arguments = args.ToList()
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a singleton service where the service type is the same as the implementation type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TClass">Class type of the service</typeparam>
|
|
||||||
public ServiceContainer AddSingleton<TClass>()
|
public ServiceContainer AddSingleton<TClass>()
|
||||||
where TClass : class {
|
where TClass : class {
|
||||||
descriptors.Add(new() {
|
AddDescriptor(typeof(TClass), typeof(TClass), ServiceLifetime.Singleton, null);
|
||||||
ServiceType = typeof(TClass),
|
|
||||||
ImplementationType = typeof(TClass),
|
|
||||||
Lifetime = ServiceLifetime.Singleton
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a singleton service with its implementation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">Arguments to be passed to the constructor, in order of appearance if of the same type.</param>
|
|
||||||
/// <typeparam name="TImplementation">Implementation type of the service</typeparam>
|
|
||||||
public ServiceContainer AddSingleton<TImplementation>(params object[] args)
|
public ServiceContainer AddSingleton<TImplementation>(params object[] args)
|
||||||
where TImplementation : class {
|
where TImplementation : class {
|
||||||
descriptors.Add(new() {
|
AddDescriptor(typeof(TImplementation), typeof(TImplementation), ServiceLifetime.Singleton, args);
|
||||||
ServiceType = typeof(TImplementation),
|
|
||||||
ImplementationType = typeof(TImplementation),
|
|
||||||
Lifetime = ServiceLifetime.Singleton,
|
|
||||||
Arguments = args.ToList()
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a transient service with its implementation.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TInterface">Interface the service is implementing</typeparam>
|
|
||||||
/// <typeparam name="TImplementation">Implementation type of the service</typeparam>
|
|
||||||
public ServiceContainer AddTransient<TInterface, TImplementation>()
|
public ServiceContainer AddTransient<TInterface, TImplementation>()
|
||||||
where TInterface : class
|
where TInterface : class
|
||||||
where TImplementation : class, TInterface {
|
where TImplementation : class, TInterface {
|
||||||
descriptors.Add(new() {
|
AddDescriptor(typeof(TInterface), typeof(TImplementation), ServiceLifetime.Transient, null);
|
||||||
ServiceType = typeof(TInterface),
|
|
||||||
ImplementationType = typeof(TImplementation),
|
|
||||||
Lifetime = ServiceLifetime.Transient
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a transient service with its implementation.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">Arguments to be passed to the constructor, in order of appearance if of the same type.</param>
|
|
||||||
/// <typeparam name="TInterface">Interface the service is implementing</typeparam>
|
|
||||||
/// <typeparam name="TImplementation">Implementation type of the service</typeparam>
|
|
||||||
public ServiceContainer AddTransient<TInterface, TImplementation>(params object[] args)
|
public ServiceContainer AddTransient<TInterface, TImplementation>(params object[] args)
|
||||||
where TInterface : class
|
where TInterface : class
|
||||||
where TImplementation : class, TInterface {
|
where TImplementation : class, TInterface {
|
||||||
descriptors.Add(new() {
|
AddDescriptor(typeof(TInterface), typeof(TImplementation), ServiceLifetime.Transient, args);
|
||||||
ServiceType = typeof(TInterface),
|
|
||||||
ImplementationType = typeof(TImplementation),
|
|
||||||
Lifetime = ServiceLifetime.Transient,
|
|
||||||
Arguments = args.ToList()
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a transient service where the service type is the same as the implementation type.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TClass">Class type of the service</typeparam>
|
|
||||||
public ServiceContainer AddTransient<TClass>()
|
public ServiceContainer AddTransient<TClass>()
|
||||||
where TClass : class {
|
where TClass : class {
|
||||||
descriptors.Add(new() {
|
AddDescriptor(typeof(TClass), typeof(TClass), ServiceLifetime.Transient, null);
|
||||||
ServiceType = typeof(TClass),
|
|
||||||
ImplementationType = typeof(TClass),
|
|
||||||
Lifetime = ServiceLifetime.Transient
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a transient service where the service type is the same as the implementation type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">Arguments to be passed to the constructor, in order of appearance if of the same type.</param>
|
|
||||||
/// <typeparam name="TClass">Class type of the service</typeparam>
|
|
||||||
public ServiceContainer AddTransient<TClass>(params object[] args)
|
public ServiceContainer AddTransient<TClass>(params object[] args)
|
||||||
where TClass : class {
|
where TClass : class {
|
||||||
descriptors.Add(new() {
|
AddDescriptor(typeof(TClass), typeof(TClass), ServiceLifetime.Transient, args);
|
||||||
ServiceType = typeof(TClass),
|
|
||||||
ImplementationType = typeof(TClass),
|
|
||||||
Lifetime = ServiceLifetime.Transient,
|
|
||||||
Arguments = args.ToList()
|
|
||||||
});
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resolves and returns an instance of the requested service type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serviceType">Type of the service that's being requested</param>
|
|
||||||
/// <param name="args">arguments to pass to the constructor of the service</param>
|
|
||||||
/// <remarks> you can't call generic methods with an unknown type at compile time
|
|
||||||
/// so we use reflection to call the generic GetService{T} method with the provided
|
|
||||||
/// type Basically we build the method GetService{serviceType}() at runtime and then call it.</remarks>
|
|
||||||
/// <returns>An object that is the instantiated service type</returns>
|
|
||||||
public object GetService(Type serviceType, object[]? args = null) {
|
|
||||||
List<Type> arguments = [serviceType];
|
|
||||||
|
|
||||||
if (args != null) arguments.AddRange(args.ToList().Select(a => a.GetType()));
|
private void AddDescriptor(Type serviceType, Type implementationType, ServiceLifetime lifetime, object[]? args) {
|
||||||
|
if (descriptors.Any(d => d.ServiceType == serviceType && d.ImplementationType == implementationType)) {
|
||||||
var method = typeof(ServiceContainer)
|
throw new InvalidOperationException(
|
||||||
.GetMethod(nameof(GetService))!
|
$"A registration for '{implementationType.Name}' as '{serviceType.Name}' already exists.");
|
||||||
.MakeGenericMethod(arguments.ToArray());
|
}
|
||||||
|
|
||||||
return method.Invoke(this, args)!;
|
descriptors.Add(new ServiceDescriptor {
|
||||||
|
ServiceType = serviceType,
|
||||||
|
ImplementationType = implementationType,
|
||||||
|
Lifetime = lifetime,
|
||||||
|
Arguments = args?.ToList()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public object GetService(Type serviceType, object[]? args = null) {
|
||||||
/// tries to resolve and return an instance of the requested service type. Returns null if it fails.
|
return ResolveService(serviceType, args);
|
||||||
/// </summary>
|
}
|
||||||
/// <param name="serviceType">Type of the service that's being requested</param>
|
|
||||||
/// <param name="args">arguments to pass to the constructor of the service</param>
|
public TService GetService<TService>() {
|
||||||
/// <remarks> you can't call generic methods with an unknown type at compile time
|
return (TService)ResolveService(typeof(TService), null);
|
||||||
/// so we use reflection to call the generic GetService{T} method with the provided
|
}
|
||||||
/// type Basically we build the method GetService{serviceType}() at runtime and then call it.</remarks>
|
|
||||||
/// <returns>An object that is the instantiated service type or null if not found</returns>
|
|
||||||
public object? TryGetService(Type serviceType, object[]? args = null) {
|
public object? TryGetService(Type serviceType, object[]? args = null) {
|
||||||
try {
|
try {
|
||||||
return GetService(serviceType, args);
|
return ResolveService(serviceType, args);
|
||||||
} catch {
|
}
|
||||||
|
catch (InvalidOperationException) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private object ResolveService(Type serviceType, object[]? resolutionArgs) {
|
||||||
/// Resolves and returns an instance of the requested service type.
|
var descriptor = descriptors.FirstOrDefault(d =>
|
||||||
/// </summary>
|
d.ServiceType == serviceType || d.ImplementationType == serviceType);
|
||||||
/// <typeparam name="TService">Interface type of the service being requested</typeparam>
|
|
||||||
/// <returns>Resolved service instance</returns>
|
|
||||||
public TService GetService<TService>() {
|
|
||||||
var descriptor = descriptors.FirstOrDefault(d => d.ServiceType == typeof(TService) || d.ImplementationType == typeof(TService));
|
|
||||||
|
|
||||||
if (descriptor == null) throw new Exception($"Service of type {typeof(TService)} not registered.");
|
if (descriptor == null) {
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Service of type '{serviceType.Name}' not registered.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergedArgs = descriptor.Arguments != null
|
||||||
|
? new List<object>(descriptor.Arguments)
|
||||||
|
: new List<object>();
|
||||||
|
|
||||||
|
if (resolutionArgs != null) {
|
||||||
|
foreach (var arg in resolutionArgs) {
|
||||||
|
var argType = arg.GetType();
|
||||||
|
var index = mergedArgs.FindIndex(a => a.GetType() == argType);
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
mergedArgs[index] = arg;
|
||||||
|
} else {
|
||||||
|
mergedArgs.Add(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var ctors = descriptor.ImplementationType.GetConstructors();
|
var ctors = descriptor.ImplementationType.GetConstructors();
|
||||||
var par = descriptor.Arguments ?? new List<object>();
|
|
||||||
int max = -1;
|
|
||||||
ConstructorInfo? bestCtor = null;
|
ConstructorInfo? bestCtor = null;
|
||||||
|
int max = -1;
|
||||||
|
|
||||||
foreach (var ctor in ctors) {
|
foreach (var ctor in ctors) {
|
||||||
var parameters = ctor.GetParameters();
|
var parameters = ctor.GetParameters();
|
||||||
//check if all parameters are registered services or optional or have been provided as arguments
|
|
||||||
if (parameters.Any(p => descriptors.All(d => d.ServiceType != p.ParameterType) && par.All(a => a.GetType() != p.ParameterType) && !p.IsOptional))
|
if (parameters.Any(p =>
|
||||||
|
descriptors.All(d => d.ServiceType != p.ParameterType) &&
|
||||||
|
mergedArgs.All(a => !p.ParameterType.IsAssignableFrom(a.GetType())) &&
|
||||||
|
!p.IsOptional)) {
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
//check if this constructor has more registered parameters than the previous best
|
int satisfied = parameters.Count(p =>
|
||||||
int satisfiedParams = parameters.Count(p => descriptors.Any(d => d.ServiceType == p.ParameterType));
|
descriptors.Any(d => d.ServiceType == p.ParameterType));
|
||||||
satisfiedParams += par.Count(arg => parameters.Any(p => p.ParameterType == arg.GetType()));
|
|
||||||
|
|
||||||
if (satisfiedParams > max) {
|
int argSatisfied = 0;
|
||||||
max = satisfiedParams;
|
|
||||||
|
foreach (var param in parameters) {
|
||||||
|
if (!descriptors.Any(d => d.ServiceType == param.ParameterType) &&
|
||||||
|
mergedArgs.Any(a => param.ParameterType.IsAssignableFrom(a.GetType()))) {
|
||||||
|
argSatisfied++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
satisfied += argSatisfied;
|
||||||
|
|
||||||
|
if (satisfied > max) {
|
||||||
|
max = satisfied;
|
||||||
bestCtor = ctor;
|
bestCtor = ctor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bestCtor == null)
|
if (bestCtor == null) {
|
||||||
throw new Exception($"Cannot create service of type {typeof(TService)}. No suitable constructor found.");
|
throw new InvalidOperationException(
|
||||||
|
$"Cannot create service of type '{serviceType.Name}'. No suitable constructor found.");
|
||||||
// Transient: create a new instance each time
|
|
||||||
if (descriptor.Lifetime != ServiceLifetime.Singleton) {
|
|
||||||
var service = Instantiate<TService>(descriptor, bestCtor);
|
|
||||||
return service;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton: return existing instance
|
if (descriptor.Lifetime == ServiceLifetime.Singleton) {
|
||||||
if (singletons.TryGetValue(descriptor.ServiceType, out object? singleton)) return (TService)singleton;
|
lock (singletonLock) {
|
||||||
|
if (singletons.TryGetValue(descriptor.ServiceType, out var singleton)) {
|
||||||
|
return singleton;
|
||||||
|
}
|
||||||
|
|
||||||
// or create a new one if not yet created.
|
var instance = Instantiate(descriptor.ImplementationType, bestCtor, mergedArgs);
|
||||||
var newSingleton = Instantiate<TService>(descriptor, bestCtor);
|
singletons[descriptor.ServiceType] = instance;
|
||||||
singletons[descriptor.ServiceType] = newSingleton!;
|
return instance;
|
||||||
return newSingleton;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Instantiate(descriptor.ImplementationType, bestCtor, mergedArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TInterface Instantiate<TInterface>(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<ParameterInfo> par;
|
private object Instantiate(Type implementationType, ConstructorInfo ctor, List<object> args) {
|
||||||
List<object> args = descriptor.Arguments != null ? new List<object>(descriptor.Arguments) : new List<object>();
|
var parameters = ctor.GetParameters();
|
||||||
|
var resolvedParams = new object?[parameters.Length];
|
||||||
|
var usedArgs = new List<object>();
|
||||||
|
|
||||||
if (ctor == null)
|
for (var i = 0; i < parameters.Length; i++) {
|
||||||
par = descriptor.ImplementationType
|
var paramType = parameters[i].ParameterType;
|
||||||
.GetConstructors().Single()
|
|
||||||
.GetParameters()
|
|
||||||
//.Select(p => p.ParameterType)
|
|
||||||
.ToList();
|
|
||||||
else
|
|
||||||
par = ctor.GetParameters()
|
|
||||||
//.Select(p => p.ParameterType)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
object[] parameters = new object[par.Count];
|
var arg = args.FirstOrDefault(a =>
|
||||||
|
!usedArgs.Contains(a) && paramType.IsAssignableFrom(a.GetType()));
|
||||||
|
|
||||||
for (int i = 0; i < par.Count; i++) {
|
|
||||||
object? arg = args.FirstOrDefault(a => a.GetType() == par[i].ParameterType);
|
|
||||||
if (arg != null) { // this parameter is satisfied by a provided argument
|
|
||||||
parameters[i] = arg;
|
|
||||||
args.Remove(arg); // remove to handle multiple parameters of the same type
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
arg = TryGetService(par[i].ParameterType);
|
|
||||||
if (arg != null) {
|
if (arg != null) {
|
||||||
// this parameter is satisfied by a registered service
|
resolvedParams[i] = arg;
|
||||||
parameters[i] = arg;
|
usedArgs.Add(arg);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (par[i].IsOptional) {
|
var ctorArg = TryGetService(paramType);
|
||||||
// this parameter is optional and not provided, use default value
|
if (ctorArg != null) {
|
||||||
parameters[i] = par[i].DefaultValue!;
|
resolvedParams[i] = ctorArg;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw new Exception($"Cannot resolve parameter {par[i].Name} of type {par[i].ParameterType} for service {descriptor.ImplementationType}");
|
|
||||||
|
if (parameters[i].IsOptional) {
|
||||||
|
resolvedParams[i] = parameters[i].DefaultValue;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Cannot resolve parameter '{parameters[i].Name}' of type '{paramType.Name}' for service '{implementationType.Name}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var service = (TInterface?)Activator.CreateInstance(descriptor.ImplementationType, parameters);
|
var instance = Activator.CreateInstance(implementationType, resolvedParams);
|
||||||
|
|
||||||
return service ?? throw new Exception($"Could not create instance of type {descriptor.ImplementationType}");
|
return instance ??
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Could not create instance of type '{implementationType.Name}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
foreach (var disposable in singletons.Values.OfType<IDisposable>()) {
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
singletons.Clear();
|
||||||
|
descriptors.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user