131 lines
5.6 KiB
C#
131 lines
5.6 KiB
C#
using System.Reflection;
|
|
|
|
namespace Syrette;
|
|
|
|
/// <summary>
|
|
/// Container for managing service registrations and resolutions.
|
|
/// </summary>
|
|
public class ServiceContainer {
|
|
private readonly List<ServiceDescriptor> descriptors = new();
|
|
private readonly Dictionary<Type, object> singletons = new();
|
|
|
|
/// <summary>
|
|
/// Get all registered implementation types for a given service type.
|
|
/// </summary>
|
|
/// <typeparam name="TServices"></typeparam>
|
|
/// <returns></returns>
|
|
public List<Type> GetServices<TServices>() =>
|
|
descriptors.Where(d => d.ServiceType == typeof(TServices))
|
|
.Select(d => d.ImplementationType).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>()
|
|
where TImplementation : class, TInterface {
|
|
descriptors.Add(new ServiceDescriptor {
|
|
ServiceType = typeof(TInterface),
|
|
ImplementationType = typeof(TImplementation),
|
|
Lifetime = ServiceLifetime.Lifetime
|
|
});
|
|
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>()
|
|
where TImplementation : class, TInterface {
|
|
descriptors.Add(new ServiceDescriptor {
|
|
ServiceType = typeof(TInterface),
|
|
ImplementationType = typeof(TImplementation),
|
|
Lifetime = ServiceLifetime.Transient
|
|
});
|
|
return this;
|
|
}
|
|
|
|
// 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.
|
|
// "Classic black magic sorcery" in reflection.
|
|
private object GetService(Type serviceType) {
|
|
var method = typeof(ServiceContainer)
|
|
.GetMethod(nameof(GetService))!
|
|
.MakeGenericMethod(serviceType);
|
|
return method.Invoke(this, null)!;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves and returns an instance of the requested service type.
|
|
/// </summary>
|
|
/// <typeparam name="TInterface">Interface type of the service being requested</typeparam>
|
|
/// <returns>Resolved service instance</returns>
|
|
public TInterface GetService<TInterface>() {
|
|
var descriptor = descriptors.FirstOrDefault(d => d.ServiceType == typeof(TInterface));
|
|
|
|
if (descriptor == null) throw new Exception($"Service of type {typeof(TInterface)} not registered.");
|
|
|
|
var ctors = descriptor.ImplementationType.GetConstructors();
|
|
int max = -1;
|
|
ConstructorInfo? bestCtor = null;
|
|
|
|
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<TInterface>(descriptor, bestCtor);
|
|
return service;
|
|
}
|
|
|
|
// Singleton: return existing instance
|
|
if (singletons.TryGetValue(descriptor.ServiceType, out object? singleton)) return (TInterface)singleton;
|
|
|
|
// or create a new one if not yet created.
|
|
var newSingleton = Instantiate<TInterface>(descriptor);
|
|
singletons[descriptor.ServiceType] = newSingleton!;
|
|
return newSingleton;
|
|
}
|
|
|
|
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<Type> 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];
|
|
|
|
for (int i = 0; i < par.Count; i++)
|
|
parameters[i] = GetService(par[i]);
|
|
|
|
var service = (TInterface?)Activator.CreateInstance(descriptor.ImplementationType, parameters);
|
|
|
|
return service ?? throw new Exception($"Could not create instance of type {descriptor.ImplementationType}");
|
|
}
|
|
} |