Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 46 additions & 98 deletions src/Container/Container.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
Expand All @@ -23,33 +24,17 @@ public sealed class Container : IContainer, IDisposable, IAsyncDisposable
/// Stores the mapping of service types to their implementation types.
/// This enables reflection-based factory compilation and testing visibility.
/// </summary>
private readonly ConcurrentDictionary<Type, Type> registrations = new();
private readonly ConcurrentDictionary<Type, Registration> registrations = new();

/// <summary>
/// Stores factory delegates for transient service registrations.
/// Each resolution will invoke the factory to create a new instance.
/// </summary>
private readonly ConcurrentDictionary<Type, Func<Container, object>> transients = new();

/// <summary>
/// Stores lazy-initialized singleton instances for singleton service registrations.
/// The instance is created once and reused for all subsequent resolutions.
/// </summary>
private readonly ConcurrentDictionary<Type, Lazy<object>> singletons = new();

// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ Internal Properties │
// └─────────────────────────────────────────────────────────────────────────────┘

/// <summary>
/// Gets a read-only view of all registered service-to-implementation type mappings.
/// </summary>
internal ReadOnlyDictionary<Type, Type> Registrations => new(registrations);

/// <summary>
/// Gets a read-only view of all resolved singleton instances.
/// Gets a read-only view of all registered services and their corresponding registrations.
/// </summary>
internal ReadOnlyDictionary<Type, object> Singletons => new(singletons.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Value));
internal ReadOnlyDictionary<Type, Registration> Registrations => registrations.AsReadOnly();

// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ Public Methods │
Expand All @@ -58,106 +43,68 @@ public sealed class Container : IContainer, IDisposable, IAsyncDisposable
/// <inheritdoc/>
public void Dispose()
{
foreach (Lazy<object> lazy in singletons.Values)
foreach (Registration registration in registrations.Values.Where(r => r.DisposeWithContainer.HasValue && r.DisposeWithContainer.Value))
{
switch (lazy.Value)
if (registration.Singleton?.IsValueCreated == true)
{
case IAsyncDisposable asyncDisposable:
asyncDisposable.DisposeAsync().AsTask().GetAwaiter().GetResult();
break;
case IDisposable disposable:
disposable.Dispose();
break;
switch (registration.Singleton.Value)
{
case IAsyncDisposable asyncDisposable:
asyncDisposable.DisposeAsync().AsTask().GetAwaiter().GetResult();
break;
case IDisposable disposable:
disposable.Dispose();
break;
}
}
}
}

/// <inheritdoc/>
public async ValueTask DisposeAsync()
{
foreach (Lazy<object> lazy in singletons.Values)
foreach (Registration registration in registrations.Values.Where(r => r.DisposeWithContainer.HasValue && r.DisposeWithContainer.Value))
{
switch (lazy.Value)
if (registration.Singleton?.IsValueCreated == true)
{
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
break;
case IDisposable disposable:
disposable.Dispose();
break;
switch (registration.Singleton.Value)
{
case IAsyncDisposable asyncDisposable:
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
break;
case IDisposable disposable:
disposable.Dispose();
break;
}
}
}
}

/// <inheritdoc/>
public void RegisterTransient<TService, TImplementation>()
where TImplementation : TService
=> transients[typeof(TService)] = CompileFactory(typeof(TImplementation));

/// <inheritdoc/>
public void RegisterTransient(Type serviceType, Type implType)
public void RegisterTransient(Type serviceType, Type implementationType)
{
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
ArgumentNullException.ThrowIfNull(implType, nameof(implType));
ArgumentNullException.ThrowIfNull(implementationType, nameof(implementationType));

transients[serviceType] = CompileFactory(implType);
registrations[serviceType] = new Registration(CompileFactory(implementationType), null, null);
}

/// <inheritdoc/>
public void RegisterTransient<TService>(Func<Container, TService> factory)
where TService : notnull
=> RegisterTransient(typeof(TService), c => factory(c)!);

/// <inheritdoc/>
public void RegisterTransient(Type serviceType, Func<Container, object> factory)
{
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
ArgumentNullException.ThrowIfNull(factory, nameof(factory));

transients[serviceType] = c => factory(c)!;
}

/// <inheritdoc/>
public void RegisterSingleton<TService, TImplementation>()
where TImplementation : TService
=> RegisterSingleton(typeof(TService), typeof(TImplementation));

/// <inheritdoc/>
public void RegisterSingleton(Type serviceType, Type implType)
public void RegisterSingleton(Type serviceType, Type implType, bool disposeWithContainer = true)
{
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
ArgumentNullException.ThrowIfNull(implType, nameof(implType));

singletons[serviceType] = new Lazy<object>(() => CompileFactory(implType)(this));
}

/// <inheritdoc/>
public void RegisterSingleton<TService>(Func<Container, TService> factory)
where TService : notnull
=> RegisterSingleton(typeof(TService), c => factory(c)!);

/// <inheritdoc/>
public void RegisterSingleton(Type serviceType, Func<Container, object> factory)
{
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
ArgumentNullException.ThrowIfNull(factory, nameof(factory));

singletons[serviceType] = new Lazy<object>(() => factory(this)!);
registrations[serviceType] = new Registration(null, new Lazy<object>(() => CompileFactory(implType)(this)), disposeWithContainer);
}

/// <inheritdoc/>
public void RegisterInstance<TService>(TService instance)
where TService : notnull
=> RegisterInstance(typeof(TService), instance);

/// <inheritdoc/>
public void RegisterInstance(Type serviceType, object instance)
public void RegisterInstance(Type serviceType, object instance, bool disposeWithContainer = true)
{
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
ArgumentNullException.ThrowIfNull(instance, nameof(instance));

registrations[serviceType] = instance.GetType();
singletons[serviceType] = new Lazy<object>(() => instance);
registrations[serviceType] = new Registration(null, new Lazy<object>(() => instance), disposeWithContainer);
}

/// <inheritdoc/>
Expand All @@ -170,24 +117,25 @@ public object Resolve(Type serviceType)
{
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));

if (singletons.TryGetValue(serviceType, out var lazy))
if (registrations.TryGetValue(serviceType, out Registration registration))
{
return lazy.Value;
}

if (transients.TryGetValue(serviceType, out var factory))
{
return factory(this);
if (registration.Singleton is not null)
{
return registration.Singleton.Value;
}
else if (registration.Factory is not null)
{
return registration.Factory(this);
}
else
{
throw new InvalidOperationException($"No factory or singleton instance found for service type: {serviceType.FullName}");
}
}

throw new InvalidOperationException($"Service not registered: {serviceType.FullName}");
}

/// <inheritdoc/>
public TService New<TService>()
where TService : notnull
=> (TService)New(typeof(TService));

/// <inheritdoc/>
public object New(Type implType)
{
Expand Down
59 changes: 16 additions & 43 deletions src/Container/IContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,83 +18,56 @@ public interface IContainer
/// <typeparam name="TService">The service type to register.</typeparam>
/// <typeparam name="TImplementation">The implementation type that implements <typeparamref name="TService"/>.</typeparam>
public void RegisterTransient<TService, TImplementation>()
where TImplementation : TService;
where TImplementation : TService
=> RegisterTransient(typeof(TService), typeof(TImplementation));

/// <summary>
/// Registers a transient service with its implementation type using type parameters.
/// A new instance will be created each time the service is resolved.
/// </summary>
/// <param name="serviceType">The service type to register.</param>
/// <param name="implType">The implementation type that implements <paramref name="serviceType"/>.</param>
public void RegisterTransient(Type serviceType, Type implType);

/// <summary>
/// Registers a transient service with a factory delegate.
/// A new instance will be created each time the service is resolved.
/// </summary>
/// <typeparam name="TService">The service type to register.</typeparam>
/// <param name="factory">A factory delegate that creates instances of the service.</param>
public void RegisterTransient<TService>(Func<Container, TService> factory)
where TService : notnull;

/// <summary>
/// Registers a transient service with a factory delegate using type parameters.
/// A new instance will be created each time the service is resolved.
/// </summary>
/// <param name="serviceType">The service type to register.</param>
/// <param name="factory">A factory delegate that creates instances of the service.</param>
public void RegisterTransient(Type serviceType, Func<Container, object> factory);
/// <param name="implementationType">The implementation type that implements <paramref name="serviceType"/>.</param>
public void RegisterTransient(Type serviceType, Type implementationType);

/// <summary>
/// Registers a singleton service with its implementation type using generic parameters.
/// A single instance will be created and reused for all resolutions.
/// </summary>
/// <typeparam name="TService">The service type to register.</typeparam>
/// <typeparam name="TImplementation">The implementation type that implements <typeparamref name="TService"/>.</typeparam>
public void RegisterSingleton<TService, TImplementation>()
where TImplementation : TService;
/// <param name="disposeWithContainer">Indicates whether the instance should be disposed when the container is disposed.</param>
public void RegisterSingleton<TService, TImplementation>(bool disposeWithContainer = true)
where TImplementation : TService
=> RegisterSingleton(typeof(TService), typeof(TImplementation), disposeWithContainer);

/// <summary>
/// Registers a singleton service with its implementation type using type parameters.
/// A single instance will be created and reused for all resolutions.
/// </summary>
/// <param name="serviceType">The service type to register.</param>
/// <param name="implType">The implementation type that implements <paramref name="serviceType"/>.</param>
public void RegisterSingleton(Type serviceType, Type implType);

/// <summary>
/// Registers a singleton service with a factory delegate.
/// A single instance will be created and reused for all resolutions.
/// </summary>
/// <typeparam name="TService">The service type to register.</typeparam>
/// <param name="factory">A factory delegate that creates the singleton instance.</param>
public void RegisterSingleton<TService>(Func<Container, TService> factory)
where TService : notnull;

/// <summary>
/// Registers a singleton service with a factory delegate using type parameters.
/// A single instance will be created and reused for all resolutions.
/// </summary>
/// <param name="serviceType">The service type to register.</param>
/// <param name="factory">A factory delegate that creates the singleton instance.</param>
public void RegisterSingleton(Type serviceType, Func<Container, object> factory);
/// <param name="disposeWithContainer">Indicates whether the instance should be disposed when the container is disposed.</param>
public void RegisterSingleton(Type serviceType, Type implType, bool disposeWithContainer = true);

/// <summary>
/// Registers a pre-created instance as a singleton service using generic parameters.
/// The provided instance will be returned for all resolutions of the service.
/// </summary>
/// <typeparam name="TService">The service type to register.</typeparam>
/// <param name="instance">The instance to register as a singleton.</param>
public void RegisterInstance<TService>(TService instance)
where TService : notnull;
/// <param name="disposeWithContainer">Indicates whether the instance should be disposed when the container is disposed.</param>
public void RegisterInstance<TService>(TService instance, bool disposeWithContainer = true)
where TService : notnull
=> RegisterInstance(typeof(TService), instance!, disposeWithContainer);

/// <summary>
/// Registers a pre-created instance as a singleton service using type parameters.
/// The provided instance will be returned for all resolutions of the service.
/// </summary>
/// <param name="serviceType">The service type to register.</param>
/// <param name="instance">The instance to register as a singleton.</param>
public void RegisterInstance(Type serviceType, object instance);
/// <param name="disposeWithContainer">Indicates whether the instance should be disposed when the container is disposed.</param>
public void RegisterInstance(Type serviceType, object instance, bool disposeWithContainer = true);

/// <summary>
/// Resolves and returns an instance of the registered service using generic parameters.
Expand Down
8 changes: 8 additions & 0 deletions src/Container/Registration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System;

namespace WB.Logging.LogSinks.Base;

internal readonly record struct Registration(
Func<Container, object>? Factory,
Lazy<object>? Singleton,
bool? DisposeWithContainer);
2 changes: 1 addition & 1 deletion src/LogMessageWriter/AsyncLogMessageWriterPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async ValueTask DisposeAsync()

public void RegisterWriter(Type logMessageWriterType, Type payloadType)
{
container.RegisterSingleton(logMessageWriterType, (c) => container.New(logMessageWriterType));
container.RegisterSingleton(logMessageWriterType, logMessageWriterType);

logMessageWriterFactories[payloadType] = () => container.Resolve(logMessageWriterType);
}
Expand Down
2 changes: 1 addition & 1 deletion src/LogMessageWriter/LogMessageWriterPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void Dispose()

public void RegisterWriter(Type logMessageWriterType, Type payloadType)
{
container.RegisterSingleton(logMessageWriterType, (c) => container.New(logMessageWriterType));
container.RegisterSingleton(logMessageWriterType, logMessageWriterType);

logMessageWriterFactories[payloadType] = () => container.Resolve(logMessageWriterType);
}
Expand Down
13 changes: 9 additions & 4 deletions src/LogSinks/AsyncLogSinkBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -83,13 +84,17 @@ public virtual void RegisterLogMessageWriter<TAsyncLogMessageWriter>()
throw new ArgumentException($"The log message writer type must implement IAsyncLogMessageWriter<TPayload> for some payload type.", nameof(TAsyncLogMessageWriter));
}

Type payloadType = logMessageWriterType.GetInterfaces()
.First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncLogMessageWriter<>))
.GetGenericArguments()[0];
IEnumerable<Type> payloadTypes = logMessageWriterType.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncLogMessageWriter<>))
.Select(i => i.GetGenericArguments()[0]);

logMessageWriterPipeline.RegisterWriter(logMessageWriterType, payloadType);
foreach (var payloadType in payloadTypes)
{
logMessageWriterPipeline.RegisterWriter(logMessageWriterType, payloadType);
}
}


// ┌─────────────────────────────────────────────────────────────────────────────┐
// │ Protected Methods │
// └─────────────────────────────────────────────────────────────────────────────┘
Expand Down
12 changes: 8 additions & 4 deletions src/LogSinks/LogSinkBase.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace WB.Logging.LogSinks.Base;
Expand Down Expand Up @@ -80,11 +81,14 @@ public virtual void RegisterLogMessageWriter<TLogMessageWriter>()
throw new ArgumentException($"The log message writer type must implement ILogMessageWriter<TPayload> for some payload type.", nameof(TLogMessageWriter));
}

Type payloadType = logMessageWriterType.GetInterfaces()
.First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ILogMessageWriter<>))
.GetGenericArguments()[0];
IEnumerable<Type> payloadTypes = logMessageWriterType.GetInterfaces()
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ILogMessageWriter<>))
.Select(i => i.GetGenericArguments()[0]);

logMessageWriterPipeline.RegisterWriter(logMessageWriterType, payloadType);
foreach (var payloadType in payloadTypes)
{
logMessageWriterPipeline.RegisterWriter(logMessageWriterType, payloadType);
}
}

// ┌─────────────────────────────────────────────────────────────────────────────┐
Expand Down
Loading
Loading