Fixes #4, renames ServiceLifetime.Lifetime to Singleton, adds support for arguments in constructors, version bumps.
All checks were successful
Nuget Pkg Build / build (push) Successful in 49s
All checks were successful
Nuget Pkg Build / build (push) Successful in 49s
This commit is contained in:
@@ -49,6 +49,16 @@ class GuidDependantService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ICacheService {
|
||||||
|
public Uri CacheLocation { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CacheService : ICacheService {
|
||||||
|
public required Uri CacheLocation { get; set; }
|
||||||
|
public CacheService() => CacheLocation = new Uri("http://default.cache");
|
||||||
|
public CacheService(Uri cacheLocation) => CacheLocation = cacheLocation;
|
||||||
|
}
|
||||||
|
|
||||||
static class Program {
|
static class Program {
|
||||||
static void Main(string[] args) {
|
static void Main(string[] args) {
|
||||||
var container = new ServiceContainer()
|
var container = new ServiceContainer()
|
||||||
@@ -63,5 +73,12 @@ static class Program {
|
|||||||
container.GetService<GuidDependantService>().LogWithId("Hello, sent from the dependency.");
|
container.GetService<GuidDependantService>().LogWithId("Hello, sent from the dependency.");
|
||||||
container.GetService<IService>().Log("Goodbye, Dependency Injection!");
|
container.GetService<IService>().Log("Goodbye, Dependency Injection!");
|
||||||
var res = container.GetServices<IService>();
|
var res = container.GetServices<IService>();
|
||||||
|
|
||||||
|
var testContainer = new ServiceContainer()
|
||||||
|
.AddSingleton<ICacheService, CacheService>(new Uri("http://cache.local"));
|
||||||
|
var iCacheService = testContainer.GetService<ICacheService>();
|
||||||
|
Console.WriteLine($"[ICacheService] {iCacheService.CacheLocation}");
|
||||||
|
var cacheService = testContainer.GetService<CacheService>();
|
||||||
|
Console.WriteLine($"[CacheService] {cacheService.CacheLocation}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,10 +14,10 @@ public class ServiceContainer {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TServices"></typeparam>
|
/// <typeparam name="TServices"></typeparam>
|
||||||
/// <returns></returns>
|
/// <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>
|
/// <summary>
|
||||||
/// Get all registered services for a given service type.
|
/// Get all registered services for a given service type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,24 +34,58 @@ public class ServiceContainer {
|
|||||||
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 () {
|
descriptors.Add(new() {
|
||||||
ServiceType = typeof(TInterface),
|
ServiceType = typeof(TInterface),
|
||||||
ImplementationType = typeof(TImplementation),
|
ImplementationType = typeof(TImplementation),
|
||||||
Lifetime = ServiceLifetime.Lifetime
|
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)
|
||||||
|
where TInterface : class
|
||||||
|
where TImplementation : class, TInterface {
|
||||||
|
descriptors.Add(new() {
|
||||||
|
ServiceType = typeof(TInterface),
|
||||||
|
ImplementationType = typeof(TImplementation),
|
||||||
|
Lifetime = ServiceLifetime.Singleton,
|
||||||
|
Arguments = args.ToList()
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a singleton service where the service type is the same as the implementation type.
|
/// Registers a singleton service where the service type is the same as the implementation type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TClass">Class type of the service</typeparam>
|
/// <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 () {
|
descriptors.Add(new() {
|
||||||
ServiceType = typeof(TClass),
|
ServiceType = typeof(TClass),
|
||||||
ImplementationType = typeof(TClass),
|
ImplementationType = typeof(TClass),
|
||||||
Lifetime = ServiceLifetime.Lifetime
|
Lifetime = ServiceLifetime.Singleton
|
||||||
|
});
|
||||||
|
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)
|
||||||
|
where TImplementation : class {
|
||||||
|
descriptors.Add(new() {
|
||||||
|
ServiceType = typeof(TImplementation),
|
||||||
|
ImplementationType = typeof(TImplementation),
|
||||||
|
Lifetime = ServiceLifetime.Singleton,
|
||||||
|
Arguments = args.ToList()
|
||||||
});
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -64,28 +98,62 @@ public class ServiceContainer {
|
|||||||
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 () {
|
descriptors.Add(new() {
|
||||||
ServiceType = typeof(TInterface),
|
ServiceType = typeof(TInterface),
|
||||||
ImplementationType = typeof(TImplementation),
|
ImplementationType = typeof(TImplementation),
|
||||||
Lifetime = ServiceLifetime.Transient
|
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)
|
||||||
|
where TInterface : class
|
||||||
|
where TImplementation : class, TInterface {
|
||||||
|
descriptors.Add(new() {
|
||||||
|
ServiceType = typeof(TInterface),
|
||||||
|
ImplementationType = typeof(TImplementation),
|
||||||
|
Lifetime = ServiceLifetime.Transient,
|
||||||
|
Arguments = args.ToList()
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a transient service where the service type is the same as the implementation type.
|
/// Registers a transient service where the service type is the same as the implementation type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TClass">Class type of the service</typeparam>
|
/// <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 () {
|
descriptors.Add(new() {
|
||||||
ServiceType = typeof(TClass),
|
ServiceType = typeof(TClass),
|
||||||
ImplementationType = typeof(TClass),
|
ImplementationType = typeof(TClass),
|
||||||
Lifetime = ServiceLifetime.Transient
|
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)
|
||||||
|
where TClass : class {
|
||||||
|
descriptors.Add(new() {
|
||||||
|
ServiceType = typeof(TClass),
|
||||||
|
ImplementationType = typeof(TClass),
|
||||||
|
Lifetime = ServiceLifetime.Transient,
|
||||||
|
Arguments = args.ToList()
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
// you can't call generic methods with an unknown type at compile time
|
// 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
|
// 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.
|
// Basically we build the method GetService<serviceType>() at runtime and then call it.
|
||||||
@@ -97,6 +165,17 @@ public class ServiceContainer {
|
|||||||
return method.Invoke(this, null)!;
|
return method.Invoke(this, null)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object? TryGetService(Type serviceType) {
|
||||||
|
var method = typeof(ServiceContainer)
|
||||||
|
.GetMethod(nameof(GetService))!
|
||||||
|
.MakeGenericMethod(serviceType);
|
||||||
|
try {
|
||||||
|
return method.Invoke(this, null)!;
|
||||||
|
} catch {
|
||||||
|
return null!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves and returns an instance of the requested service type.
|
/// Resolves and returns an instance of the requested service type.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -104,38 +183,44 @@ public class ServiceContainer {
|
|||||||
/// <returns>Resolved service instance</returns>
|
/// <returns>Resolved service instance</returns>
|
||||||
public TService GetService<TService>() {
|
public TService GetService<TService>() {
|
||||||
var descriptor = descriptors.FirstOrDefault(d => d.ServiceType == typeof(TService) || d.ImplementationType == typeof(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 Exception($"Service of type {typeof(TService)} not registered.");
|
||||||
|
|
||||||
var ctors = descriptor.ImplementationType.GetConstructors();
|
var ctors = descriptor.ImplementationType.GetConstructors();
|
||||||
|
var par = descriptor.Arguments ?? new List<object>();
|
||||||
int max = -1;
|
int max = -1;
|
||||||
ConstructorInfo? bestCtor = null;
|
ConstructorInfo? bestCtor = null;
|
||||||
|
|
||||||
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
|
//check if all parameters are registered services or optional or have been provided as arguments
|
||||||
if (!parameters.All(p => descriptors.Any(d => d.ServiceType == p.ParameterType) || p.IsOptional)) continue;
|
if (parameters.Any(p => descriptors.All(d => d.ServiceType != p.ParameterType) && par.All(a => a.GetType() != p.ParameterType) && !p.IsOptional))
|
||||||
|
continue;
|
||||||
|
|
||||||
//check if this constructor has more registered parameters than the previous best
|
//check if this constructor has more registered parameters than the previous best
|
||||||
int satisfiedParams = parameters.Count(p => descriptors.Any(d => d.ServiceType == p.ParameterType));
|
int satisfiedParams = parameters.Count(p => descriptors.Any(d => d.ServiceType == p.ParameterType));
|
||||||
|
satisfiedParams += par.Count(arg => parameters.Any(p => p.ParameterType == arg.GetType()));
|
||||||
|
|
||||||
if (satisfiedParams > max) {
|
if (satisfiedParams > max) {
|
||||||
max = satisfiedParams;
|
max = satisfiedParams;
|
||||||
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 Exception($"Cannot create service of type {typeof(TService)}. 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.Singleton) {
|
||||||
var service = Instantiate<TService>(descriptor, bestCtor);
|
var service = Instantiate<TService>(descriptor, bestCtor);
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Singleton: return existing instance
|
// Singleton: return existing instance
|
||||||
if (singletons.TryGetValue(descriptor.ServiceType, out object? singleton)) return (TService)singleton;
|
if (singletons.TryGetValue(descriptor.ServiceType, out object? singleton)) return (TService)singleton;
|
||||||
|
|
||||||
// or create a new one if not yet created.
|
// or create a new one if not yet created.
|
||||||
var newSingleton = Instantiate<TService>(descriptor);
|
var newSingleton = Instantiate<TService>(descriptor, bestCtor);
|
||||||
singletons[descriptor.ServiceType] = newSingleton!;
|
singletons[descriptor.ServiceType] = newSingleton!;
|
||||||
return newSingleton;
|
return newSingleton;
|
||||||
}
|
}
|
||||||
@@ -143,25 +228,46 @@ public class ServiceContainer {
|
|||||||
private TInterface Instantiate<TInterface>(ServiceDescriptor descriptor, ConstructorInfo? ctor = null) {
|
private TInterface Instantiate<TInterface>(ServiceDescriptor descriptor, ConstructorInfo? ctor = null) {
|
||||||
if (ctor == null && descriptor.ImplementationType.GetConstructors().Length > 1)
|
if (ctor == null && descriptor.ImplementationType.GetConstructors().Length > 1)
|
||||||
throw new Exception($"Multiple constructors found for type {descriptor.ImplementationType}. Please provide a specific constructor.");
|
throw new Exception($"Multiple constructors found for type {descriptor.ImplementationType}. Please provide a specific constructor.");
|
||||||
|
|
||||||
List<Type> par;
|
List<ParameterInfo> par;
|
||||||
|
List<object> args = descriptor.Arguments ?? new List<object>();
|
||||||
|
|
||||||
if (ctor == null)
|
if (ctor == null)
|
||||||
par = descriptor.ImplementationType
|
par = descriptor.ImplementationType
|
||||||
.GetConstructors().Single()
|
.GetConstructors().Single()
|
||||||
.GetParameters()
|
.GetParameters()
|
||||||
.Select(p => p.ParameterType)
|
//.Select(p => p.ParameterType)
|
||||||
.ToList();
|
.ToList();
|
||||||
else
|
else
|
||||||
par = ctor.GetParameters()
|
par = ctor.GetParameters()
|
||||||
.Select(p => p.ParameterType)
|
//.Select(p => p.ParameterType)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
object[] parameters = new object[par.Count];
|
object[] parameters = new object[par.Count];
|
||||||
|
|
||||||
for (int i = 0; i < par.Count; i++)
|
for (int i = 0; i < par.Count; i++) {
|
||||||
parameters[i] = GetService(par[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) {
|
||||||
|
// this parameter is satisfied by a registered service
|
||||||
|
parameters[i] = arg;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (par[i].IsOptional) {
|
||||||
|
// this parameter is optional and not provided, use default value
|
||||||
|
parameters[i] = par[i].DefaultValue!;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new Exception($"Cannot resolve parameter {par[i].Name} of type {par[i].ParameterType} for service {descriptor.ImplementationType}");
|
||||||
|
}
|
||||||
|
|
||||||
var service = (TInterface?)Activator.CreateInstance(descriptor.ImplementationType, parameters);
|
var service = (TInterface?)Activator.CreateInstance(descriptor.ImplementationType, parameters);
|
||||||
|
|
||||||
return service ?? throw new Exception($"Could not create instance of type {descriptor.ImplementationType}");
|
return service ?? throw new Exception($"Could not create instance of type {descriptor.ImplementationType}");
|
||||||
|
|||||||
@@ -19,4 +19,9 @@ 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>
|
||||||
|
/// Arguments to be passed to the constructor of the implementation type.
|
||||||
|
/// </summary>
|
||||||
|
public List<object>? Arguments { get; set; }
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ public enum ServiceLifetime {
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a singleton service, which is created once and shared throughout the application's lifetime.
|
/// Defines a singleton service, which is created once and shared throughout the application's lifetime.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Lifetime,
|
Singleton,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines a transient service, which is created anew each time it is requested.
|
/// Defines a transient service, which is created anew each time it is requested.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Version>0.0.1.5-alpha</Version>
|
<Version>0.0.1.6-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>
|
||||||
|
|||||||
Reference in New Issue
Block a user