Skip to content

Commit e527659

Browse files
authored
Merge pull request #48 from WB-Tooling/container-refactoring
container refactoring
2 parents 1e3b21e + 2686e04 commit e527659

13 files changed

Lines changed: 111 additions & 341 deletions

src/Container/Container.cs

Lines changed: 46 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
34
using System.Collections.ObjectModel;
45
using System.Linq;
56
using System.Linq.Expressions;
@@ -23,33 +24,17 @@ public sealed class Container : IContainer, IDisposable, IAsyncDisposable
2324
/// Stores the mapping of service types to their implementation types.
2425
/// This enables reflection-based factory compilation and testing visibility.
2526
/// </summary>
26-
private readonly ConcurrentDictionary<Type, Type> registrations = new();
27+
private readonly ConcurrentDictionary<Type, Registration> registrations = new();
2728

28-
/// <summary>
29-
/// Stores factory delegates for transient service registrations.
30-
/// Each resolution will invoke the factory to create a new instance.
31-
/// </summary>
32-
private readonly ConcurrentDictionary<Type, Func<Container, object>> transients = new();
33-
34-
/// <summary>
35-
/// Stores lazy-initialized singleton instances for singleton service registrations.
36-
/// The instance is created once and reused for all subsequent resolutions.
37-
/// </summary>
38-
private readonly ConcurrentDictionary<Type, Lazy<object>> singletons = new();
3929

4030
// ┌─────────────────────────────────────────────────────────────────────────────┐
4131
// │ Internal Properties │
4232
// └─────────────────────────────────────────────────────────────────────────────┘
4333

4434
/// <summary>
45-
/// Gets a read-only view of all registered service-to-implementation type mappings.
46-
/// </summary>
47-
internal ReadOnlyDictionary<Type, Type> Registrations => new(registrations);
48-
49-
/// <summary>
50-
/// Gets a read-only view of all resolved singleton instances.
35+
/// Gets a read-only view of all registered services and their corresponding registrations.
5136
/// </summary>
52-
internal ReadOnlyDictionary<Type, object> Singletons => new(singletons.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Value));
37+
internal ReadOnlyDictionary<Type, Registration> Registrations => registrations.AsReadOnly();
5338

5439
// ┌─────────────────────────────────────────────────────────────────────────────┐
5540
// │ Public Methods │
@@ -58,106 +43,68 @@ public sealed class Container : IContainer, IDisposable, IAsyncDisposable
5843
/// <inheritdoc/>
5944
public void Dispose()
6045
{
61-
foreach (Lazy<object> lazy in singletons.Values)
46+
foreach (Registration registration in registrations.Values.Where(r => r.DisposeWithContainer.HasValue && r.DisposeWithContainer.Value))
6247
{
63-
switch (lazy.Value)
48+
if (registration.Singleton?.IsValueCreated == true)
6449
{
65-
case IAsyncDisposable asyncDisposable:
66-
asyncDisposable.DisposeAsync().AsTask().GetAwaiter().GetResult();
67-
break;
68-
case IDisposable disposable:
69-
disposable.Dispose();
70-
break;
50+
switch (registration.Singleton.Value)
51+
{
52+
case IAsyncDisposable asyncDisposable:
53+
asyncDisposable.DisposeAsync().AsTask().GetAwaiter().GetResult();
54+
break;
55+
case IDisposable disposable:
56+
disposable.Dispose();
57+
break;
58+
}
7159
}
7260
}
7361
}
7462

7563
/// <inheritdoc/>
7664
public async ValueTask DisposeAsync()
7765
{
78-
foreach (Lazy<object> lazy in singletons.Values)
66+
foreach (Registration registration in registrations.Values.Where(r => r.DisposeWithContainer.HasValue && r.DisposeWithContainer.Value))
7967
{
80-
switch (lazy.Value)
68+
if (registration.Singleton?.IsValueCreated == true)
8169
{
82-
case IAsyncDisposable asyncDisposable:
83-
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
84-
break;
85-
case IDisposable disposable:
86-
disposable.Dispose();
87-
break;
70+
switch (registration.Singleton.Value)
71+
{
72+
case IAsyncDisposable asyncDisposable:
73+
await asyncDisposable.DisposeAsync().ConfigureAwait(false);
74+
break;
75+
case IDisposable disposable:
76+
disposable.Dispose();
77+
break;
78+
}
8879
}
8980
}
9081
}
9182

9283
/// <inheritdoc/>
93-
public void RegisterTransient<TService, TImplementation>()
94-
where TImplementation : TService
95-
=> transients[typeof(TService)] = CompileFactory(typeof(TImplementation));
96-
97-
/// <inheritdoc/>
98-
public void RegisterTransient(Type serviceType, Type implType)
84+
public void RegisterTransient(Type serviceType, Type implementationType)
9985
{
10086
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
101-
ArgumentNullException.ThrowIfNull(implType, nameof(implType));
87+
ArgumentNullException.ThrowIfNull(implementationType, nameof(implementationType));
10288

103-
transients[serviceType] = CompileFactory(implType);
89+
registrations[serviceType] = new Registration(CompileFactory(implementationType), null, null);
10490
}
10591

10692
/// <inheritdoc/>
107-
public void RegisterTransient<TService>(Func<Container, TService> factory)
108-
where TService : notnull
109-
=> RegisterTransient(typeof(TService), c => factory(c)!);
110-
111-
/// <inheritdoc/>
112-
public void RegisterTransient(Type serviceType, Func<Container, object> factory)
113-
{
114-
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
115-
ArgumentNullException.ThrowIfNull(factory, nameof(factory));
116-
117-
transients[serviceType] = c => factory(c)!;
118-
}
119-
120-
/// <inheritdoc/>
121-
public void RegisterSingleton<TService, TImplementation>()
122-
where TImplementation : TService
123-
=> RegisterSingleton(typeof(TService), typeof(TImplementation));
124-
125-
/// <inheritdoc/>
126-
public void RegisterSingleton(Type serviceType, Type implType)
93+
public void RegisterSingleton(Type serviceType, Type implType, bool disposeWithContainer = true)
12794
{
12895
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
12996
ArgumentNullException.ThrowIfNull(implType, nameof(implType));
13097

131-
singletons[serviceType] = new Lazy<object>(() => CompileFactory(implType)(this));
132-
}
133-
134-
/// <inheritdoc/>
135-
public void RegisterSingleton<TService>(Func<Container, TService> factory)
136-
where TService : notnull
137-
=> RegisterSingleton(typeof(TService), c => factory(c)!);
138-
139-
/// <inheritdoc/>
140-
public void RegisterSingleton(Type serviceType, Func<Container, object> factory)
141-
{
142-
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
143-
ArgumentNullException.ThrowIfNull(factory, nameof(factory));
144-
145-
singletons[serviceType] = new Lazy<object>(() => factory(this)!);
98+
registrations[serviceType] = new Registration(null, new Lazy<object>(() => CompileFactory(implType)(this)), disposeWithContainer);
14699
}
147100

148101
/// <inheritdoc/>
149-
public void RegisterInstance<TService>(TService instance)
150-
where TService : notnull
151-
=> RegisterInstance(typeof(TService), instance);
152-
153-
/// <inheritdoc/>
154-
public void RegisterInstance(Type serviceType, object instance)
102+
public void RegisterInstance(Type serviceType, object instance, bool disposeWithContainer = true)
155103
{
156104
ArgumentNullException.ThrowIfNull(serviceType, nameof(serviceType));
157105
ArgumentNullException.ThrowIfNull(instance, nameof(instance));
158106

159-
registrations[serviceType] = instance.GetType();
160-
singletons[serviceType] = new Lazy<object>(() => instance);
107+
registrations[serviceType] = new Registration(null, new Lazy<object>(() => instance), disposeWithContainer);
161108
}
162109

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

173-
if (singletons.TryGetValue(serviceType, out var lazy))
120+
if (registrations.TryGetValue(serviceType, out Registration registration))
174121
{
175-
return lazy.Value;
176-
}
177-
178-
if (transients.TryGetValue(serviceType, out var factory))
179-
{
180-
return factory(this);
122+
if (registration.Singleton is not null)
123+
{
124+
return registration.Singleton.Value;
125+
}
126+
else if (registration.Factory is not null)
127+
{
128+
return registration.Factory(this);
129+
}
130+
else
131+
{
132+
throw new InvalidOperationException($"No factory or singleton instance found for service type: {serviceType.FullName}");
133+
}
181134
}
182135

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

186-
/// <inheritdoc/>
187-
public TService New<TService>()
188-
where TService : notnull
189-
=> (TService)New(typeof(TService));
190-
191139
/// <inheritdoc/>
192140
public object New(Type implType)
193141
{

src/Container/IContainer.cs

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -18,83 +18,56 @@ public interface IContainer
1818
/// <typeparam name="TService">The service type to register.</typeparam>
1919
/// <typeparam name="TImplementation">The implementation type that implements <typeparamref name="TService"/>.</typeparam>
2020
public void RegisterTransient<TService, TImplementation>()
21-
where TImplementation : TService;
21+
where TImplementation : TService
22+
=> RegisterTransient(typeof(TService), typeof(TImplementation));
2223

2324
/// <summary>
2425
/// Registers a transient service with its implementation type using type parameters.
2526
/// A new instance will be created each time the service is resolved.
2627
/// </summary>
2728
/// <param name="serviceType">The service type to register.</param>
28-
/// <param name="implType">The implementation type that implements <paramref name="serviceType"/>.</param>
29-
public void RegisterTransient(Type serviceType, Type implType);
30-
31-
/// <summary>
32-
/// Registers a transient service with a factory delegate.
33-
/// A new instance will be created each time the service is resolved.
34-
/// </summary>
35-
/// <typeparam name="TService">The service type to register.</typeparam>
36-
/// <param name="factory">A factory delegate that creates instances of the service.</param>
37-
public void RegisterTransient<TService>(Func<Container, TService> factory)
38-
where TService : notnull;
39-
40-
/// <summary>
41-
/// Registers a transient service with a factory delegate using type parameters.
42-
/// A new instance will be created each time the service is resolved.
43-
/// </summary>
44-
/// <param name="serviceType">The service type to register.</param>
45-
/// <param name="factory">A factory delegate that creates instances of the service.</param>
46-
public void RegisterTransient(Type serviceType, Func<Container, object> factory);
29+
/// <param name="implementationType">The implementation type that implements <paramref name="serviceType"/>.</param>
30+
public void RegisterTransient(Type serviceType, Type implementationType);
4731

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

5743
/// <summary>
5844
/// Registers a singleton service with its implementation type using type parameters.
5945
/// A single instance will be created and reused for all resolutions.
6046
/// </summary>
6147
/// <param name="serviceType">The service type to register.</param>
6248
/// <param name="implType">The implementation type that implements <paramref name="serviceType"/>.</param>
63-
public void RegisterSingleton(Type serviceType, Type implType);
64-
65-
/// <summary>
66-
/// Registers a singleton service with a factory delegate.
67-
/// A single instance will be created and reused for all resolutions.
68-
/// </summary>
69-
/// <typeparam name="TService">The service type to register.</typeparam>
70-
/// <param name="factory">A factory delegate that creates the singleton instance.</param>
71-
public void RegisterSingleton<TService>(Func<Container, TService> factory)
72-
where TService : notnull;
73-
74-
/// <summary>
75-
/// Registers a singleton service with a factory delegate using type parameters.
76-
/// A single instance will be created and reused for all resolutions.
77-
/// </summary>
78-
/// <param name="serviceType">The service type to register.</param>
79-
/// <param name="factory">A factory delegate that creates the singleton instance.</param>
80-
public void RegisterSingleton(Type serviceType, Func<Container, object> factory);
49+
/// <param name="disposeWithContainer">Indicates whether the instance should be disposed when the container is disposed.</param>
50+
public void RegisterSingleton(Type serviceType, Type implType, bool disposeWithContainer = true);
8151

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

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

9972
/// <summary>
10073
/// Resolves and returns an instance of the registered service using generic parameters.

src/Container/Registration.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System;
2+
3+
namespace WB.Logging.LogSinks.Base;
4+
5+
internal readonly record struct Registration(
6+
Func<Container, object>? Factory,
7+
Lazy<object>? Singleton,
8+
bool? DisposeWithContainer);

src/LogMessageWriter/AsyncLogMessageWriterPipeline.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public async ValueTask DisposeAsync()
2727

2828
public void RegisterWriter(Type logMessageWriterType, Type payloadType)
2929
{
30-
container.RegisterSingleton(logMessageWriterType, (c) => container.New(logMessageWriterType));
30+
container.RegisterSingleton(logMessageWriterType, logMessageWriterType);
3131

3232
logMessageWriterFactories[payloadType] = () => container.Resolve(logMessageWriterType);
3333
}

src/LogMessageWriter/LogMessageWriterPipeline.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void Dispose()
2525

2626
public void RegisterWriter(Type logMessageWriterType, Type payloadType)
2727
{
28-
container.RegisterSingleton(logMessageWriterType, (c) => container.New(logMessageWriterType));
28+
container.RegisterSingleton(logMessageWriterType, logMessageWriterType);
2929

3030
logMessageWriterFactories[payloadType] = () => container.Resolve(logMessageWriterType);
3131
}

src/LogSinks/AsyncLogSinkBase.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics.CodeAnalysis;
34
using System.Linq;
45
using System.Threading;
@@ -83,13 +84,17 @@ public virtual void RegisterLogMessageWriter<TAsyncLogMessageWriter>()
8384
throw new ArgumentException($"The log message writer type must implement IAsyncLogMessageWriter<TPayload> for some payload type.", nameof(TAsyncLogMessageWriter));
8485
}
8586

86-
Type payloadType = logMessageWriterType.GetInterfaces()
87-
.First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncLogMessageWriter<>))
88-
.GetGenericArguments()[0];
87+
IEnumerable<Type> payloadTypes = logMessageWriterType.GetInterfaces()
88+
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IAsyncLogMessageWriter<>))
89+
.Select(i => i.GetGenericArguments()[0]);
8990

90-
logMessageWriterPipeline.RegisterWriter(logMessageWriterType, payloadType);
91+
foreach (var payloadType in payloadTypes)
92+
{
93+
logMessageWriterPipeline.RegisterWriter(logMessageWriterType, payloadType);
94+
}
9195
}
9296

97+
9398
// ┌─────────────────────────────────────────────────────────────────────────────┐
9499
// │ Protected Methods │
95100
// └─────────────────────────────────────────────────────────────────────────────┘

src/LogSinks/LogSinkBase.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34

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

83-
Type payloadType = logMessageWriterType.GetInterfaces()
84-
.First(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ILogMessageWriter<>))
85-
.GetGenericArguments()[0];
84+
IEnumerable<Type> payloadTypes = logMessageWriterType.GetInterfaces()
85+
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(ILogMessageWriter<>))
86+
.Select(i => i.GetGenericArguments()[0]);
8687

87-
logMessageWriterPipeline.RegisterWriter(logMessageWriterType, payloadType);
88+
foreach (var payloadType in payloadTypes)
89+
{
90+
logMessageWriterPipeline.RegisterWriter(logMessageWriterType, payloadType);
91+
}
8892
}
8993

9094
// ┌─────────────────────────────────────────────────────────────────────────────┐

0 commit comments

Comments
 (0)