-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSingletonGenericServiceFactory.cs
More file actions
159 lines (140 loc) · 6.33 KB
/
SingletonGenericServiceFactory.cs
File metadata and controls
159 lines (140 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#region License
/* ************************************************************
*
* @author Couchbase <info@couchbase.com>
* @copyright 2025 Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* ************************************************************/
#endregion
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using Couchbase.AnalyticsClient.Utils;
namespace Couchbase.AnalyticsClient.Internal.DI;
/// <summary>
/// Implementation of <see cref="IServiceFactory"/> which constructs more specific types
/// from a non-specific generic with the same number of type arguments. Keeps a singleton
/// of each type to return on subsequent calls.
/// </summary>
/// <remarks>
/// <para>
/// For example, new SingletonGenericServiceFactory(typeof(Logger<>)) could be registered
/// against ILogger<>. A request for ILogger<SomeType> would return a singleton
/// of the more specific implementation Logger<SomeType>.
/// </para>
/// <para>
/// This factory must be registered against a generic type with the same number of type arguments
/// and the same or stricter type argument constraints.
/// </para>
/// <para>
/// For trimming compatibility, it is imperative that the interface registered as the service in DI
/// have DynamicallyAccessedMembers annotations on the type arguments that match the ones on the concrete
/// implementation. For example, if <c>interface IMyInterface<T></c> is the type being requested from DI and the concrete
/// implementation passed to this factory is <c>class MyClass<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T></c>
/// then <c>IMyInterface<T></c> must have the same annotation on the type argument <c>T</c>.
/// See https://github.com/dotnet/runtime/blob/7c00b17be1b2ffb6ed49ad68cf36e9a056323152/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs#L94-L98
/// </para>
/// </remarks>
internal sealed class SingletonGenericServiceFactory : IServiceFactory
{
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
private readonly Type _genericType;
private readonly ConcurrentDictionary<Type, object> _singletons = new ConcurrentDictionary<Type, object>();
private IServiceProvider? _serviceProvider;
private Func<Type, object>? _factoryDelegateCache;
/// <summary>
/// Creates a new SingletonGenericServiceFactory.
/// </summary>
/// <param name="genericType">Non-specific generic type, i.e. Logger<> to construct.</param>
/// <exception cref="ArgumentException">Not a generic type definition.</exception>
public SingletonGenericServiceFactory([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type genericType)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (genericType is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(genericType));
}
if (!genericType.IsGenericTypeDefinition)
{
ThrowHelper.ThrowArgumentException("Not a generic type definition.", nameof(genericType));
}
if (!genericType.IsClass)
{
// Must be a class to be NativeAOT compatible with MakeGenericType in the Factory below. Value types
// require unique compilation whereas classes share the same compilation.
ThrowHelper.ThrowArgumentException("Not a class.", nameof(genericType));
}
_genericType = genericType;
}
/// <inheritdoc />
public void Initialize(IServiceProvider serviceProvider)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (serviceProvider is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(serviceProvider));
}
_serviceProvider = serviceProvider;
}
/// <inheritdoc />
public object CreateService(Type requestedType)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (requestedType is null)
{
ThrowHelper.ThrowArgumentNullException(nameof(requestedType));
}
return _singletons.GetOrAdd(requestedType, _factoryDelegateCache ??= Factory);
}
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2055",
Justification = "The generic interface type arguments should have matching DynamicallyAccessedMembers to the implementation's type arguments.")]
[UnconditionalSuppressMessage("Aot", "IL3050",
Justification = "The _genericType is always a class and is therefore NativeAOT compatible.")]
private object Factory(Type requestedType)
{
if (_serviceProvider == null)
{
ThrowHelper.ThrowInvalidOperationException($"ServiceProvider {nameof(_serviceProvider)} is not initialized");
}
var typeArgs = requestedType.GetGenericArguments();
var type = _genericType.MakeGenericType(typeArgs);
var constructor = ConstructorSelector.SelectConstructor(type);
var constructorArgs = constructor.GetParameters()
.Select(p => _serviceProvider.GetRequiredService(p.ParameterType))
.ToArray();
return constructor.Invoke(constructorArgs);
}
public void Dispose()
{
var exceptions = new List<Exception>();
foreach (var singleton in _singletons.Values)
{
if (singleton is IDisposable disposable)
{
try
{
disposable.Dispose();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
}
if (exceptions.Count > 0)
{
throw new AggregateException(exceptions);
}
}
}