私たちのプロジェクトでは、SimpleInjectorを使用しており、それを愛しています! 私たちは、サービスの登録後の登録は、以下のスレッドの安全性の要求に準拠して検証することをコンテナに拡張子を追加したい:コンテナ登録のスレッド安全性を確認する方法
- 場合は登録されているすべてのシングルトンが自作の[スレッドセーフ]
- で注釈付けする必要があります[ThreadSafe]でサービスに注釈を付けると、実装タイプも[ThreadSafe]に登録する必要があります。
またGetCurrentRegistrations()
のようなオープンジェネリック型の登録によって提供されていませんデータSimpleInjectorを追跡する同様の登録方法を公開しSimpleInjectorのコンテナを、ラップThreadSafetyVerifyingContainer
を持っていることによって、今このことを確認します。このアプローチは機能しますが、条件付き登録でFuncファクトリをサポートするのに苦労するような制限があり、コンテナにVerify
を呼び出してスレッドセーフな検証を挿入するための検証プロセスに突入する方法があるかどうか自問します。現在のコードは次のとおりです:
/// <summary>
/// A wrapper around C that exposes the subset of <see cref="Container"/> API required for registration at the moment,
/// that during <see cref="Verify"/> it verifies registrations using the underline container and additionally
/// verifies that current registrations conform to thread-safety expectations.
/// </summary>
public class ThreadSafetyVerifyingContainer
{
public ThreadSafetyVerifyingContainer()
{
_underlineContainer = new Container();
_openGenericExplicitRegistrations = new List<RegistrationDescription>();
_threadSafetyExternalAnnotations = new HashSet<Type>();
MarkWellKnownExternalThreadSafeTypes();
}
public ContainerOptions Options => _underlineContainer.Options;
public Container Verify(VerificationOption options = VerificationOption.VerifyAndDiagnose)
{
_underlineContainer.Verify(options);
VerifyThreadSafety();
return _underlineContainer;
}
// Marks a type as thread-safe as a replacement to applying <see cref="ThreadSafeAttribute"/> to a type.
// Should be applied to external types not defined in the application, since <see cref="ThreadSafeAttribute"/>
// exists also for documentation purposes to let developers know some types are expected to be thread safe.
public void MarkExternalTypeAsThreadSafe(Type externalType)
{
_threadSafetyExternalAnnotations.Add(externalType);
}
private void VerifyThreadSafety()
{
var allRegistrationsDescriptions = _underlineContainer
.GetCurrentRegistrations()
.Select(RegistrationDescription.FromInstanceProducer)
.Concat(_openGenericExplicitRegistrations)
.ToArray();
var invalidSingletonRegistrations = allRegistrationsDescriptions
.Where(registration =>
registration.Lifestyle == Lifestyle.Singleton &&
!IsImplementationTypeMarkedAsThreadSafe(registration))
.Select(registration =>
$"The type {registration.ImplementationType} is registered as singleton but isn't marked as thread-safe using {nameof(ThreadSafeAttribute)}");
var invalidThreadSafeServiceRegistrations = allRegistrationsDescriptions
.Where(registration =>
IsTypeMarkedAsThreadSafe(registration.ServiceType) &&
!IsImplementationTypeMarkedAsThreadSafe(registration))
.Select(registration =>
$"The type {registration.ImplementationType} isn't marked as thread-safe using {nameof(ThreadSafeAttribute)} and is registered as implementation of {registration.ServiceType} which is marked as thread-safe");
var violations = invalidSingletonRegistrations.Concat(invalidThreadSafeServiceRegistrations).ToArray();
if (violations.Length > 0)
{
string errorMessage =
$"The container has thread-safety violating registrations:{Environment.NewLine}{string.Join(Environment.NewLine, violations)}";
throw new ThreadSafetyViolationException(errorMessage);
}
}
private void MarkWellKnownExternalThreadSafeTypes()
{
MarkExternalTypeAsThreadSafe(typeof(Container)); // SimpleInjector's Container is registered as singleton and is thread safe, ignore it
MarkExternalTypeAsThreadSafe(typeof(IEnumerable<>)); // Collections are IEnumerable<> implementations registered by the container as singletons, ignore them
}
private bool IsImplementationTypeMarkedAsThreadSafe(RegistrationDescription registration)
{
return IsTypeMarkedAsThreadSafe(registration.ImplementationType);
}
private bool IsTypeMarkedAsThreadSafe(Type type)
{
return
type.GetCustomAttribute<ThreadSafeAttribute>() != null ||
_threadSafetyExternalAnnotations.Contains(type) ||
_threadSafetyExternalAnnotations.Any(threadSafeType => threadSafeType.IsGenericTypeDefinition && type.IsGenericOf(threadSafeType));
}
private readonly HashSet<Type> _threadSafetyExternalAnnotations;
#endregion
public void Register<TService, TImplementation>(Lifestyle lifestyle)
where TService : class where TImplementation : class, TService
{
_underlineContainer.Register<TService, TImplementation>(lifestyle);
}
public void Register(Type openGenericServiceType, Assembly assembly, Lifestyle lifestyle)
{
_underlineContainer.Register(openGenericServiceType, new [] { assembly }, lifestyle);
}
public void RegisterSingleton<TService, TImplementation>()
where TImplementation : class, TService
where TService : class
{
_underlineContainer.RegisterSingleton<TService, TImplementation>();
}
public void RegisterSingleton<TConcrete>()
where TConcrete : class
{
_underlineContainer.RegisterSingleton<TConcrete>();
}
public void RegisterSingleton<TService>(TService instance) where TService : class
{
_underlineContainer.RegisterSingleton(instance);
}
public void RegisterScoped<TService, TImplementation>()
where TImplementation : class, TService
where TService : class
{
_underlineContainer.Register<TService, TImplementation>(Lifestyle.Scoped);
}
public void RegisterTransient<TService, TImplementation>()
where TService : class where TImplementation : class, TService
{
_underlineContainer.Register<TService, TImplementation>();
}
public void RegisterTransient<TImplementation>()
where TImplementation : class
{
_underlineContainer.Register<TImplementation>();
}
public void RegisterTransient<TService>(Func<TService> instanceCreator)
where TService : class
{
_underlineContainer.Register(instanceCreator);
}
public void RegisterCollectionScoped<TService>(IEnumerable<Type> concreteTypes)
where TService : class
{
_underlineContainer.RegisterCollection<TService>(concreteTypes.Select(type => Lifestyle.Scoped.CreateRegistration(type, _underlineContainer)));
}
public void Register(Type serviceType, Type implementationType, Lifestyle lifestyle)
{
_underlineContainer.Register(serviceType, implementationType, lifestyle);
RecordIfExplicitOpenGenericRegistration(serviceType, implementationType, lifestyle);
}
public void RegisterConditional(Type serviceType, Type implementationType, Lifestyle lifestyle, Predicate<PredicateContext> predicate)
{
_underlineContainer.RegisterConditional(serviceType, implementationType, lifestyle, predicate);
RecordIfExplicitOpenGenericRegistration(serviceType, implementationType, lifestyle);
}
public void RegisterImplementationByConsumerContext(Type notGenericServiceType, Type openGenericImplementationType, Lifestyle lifestyle)
{
Guard.CheckNullArgument(notGenericServiceType, nameof(notGenericServiceType));
Guard.CheckNullArgument(openGenericImplementationType, nameof(openGenericImplementationType));
Guard.CheckArgument(notGenericServiceType.IsGenericType, nameof(notGenericServiceType), $"Type {notGenericServiceType} shouldn't be a generic type but it is");
Guard.CheckArgument(!openGenericImplementationType.ContainsGenericParameters, nameof(openGenericImplementationType), $"Type {openGenericImplementationType} isn't an open-generic type");
Guard.CheckArgument(openGenericImplementationType.GetGenericArguments().Length != 1, nameof(openGenericImplementationType), $"Type {openGenericImplementationType} is open-generic but has more than 1 generic parameter");
_underlineContainer.RegisterConditional(
notGenericServiceType,
context => openGenericImplementationType.MakeGenericType(context.Consumer.ImplementationType),
lifestyle,
context => true);
RecordIfExplicitOpenGenericRegistration(notGenericServiceType, openGenericImplementationType, lifestyle);
}
private void RecordIfExplicitOpenGenericRegistration(Type serviceType, Type implementationType, Lifestyle lifestyle)
{
if (implementationType.ContainsGenericParameters)
{
_openGenericExplicitRegistrations.Add(new RegistrationDescription(serviceType, implementationType, lifestyle));
}
}
private readonly List<RegistrationDescription> _openGenericExplicitRegistrations;
[DebuggerDisplay("{ServiceType.Name} -> {ImplementationType.Name} with {Lifestyle.Name} life style")]
private class RegistrationDescription
{
public RegistrationDescription(Type serviceType, Type implementationType, Lifestyle lifestyle)
{
ServiceType = serviceType;
ImplementationType = implementationType;
Lifestyle = lifestyle;
}
public static RegistrationDescription FromInstanceProducer(InstanceProducer instanceProducer)
{
return new RegistrationDescription(
instanceProducer.ServiceType,
instanceProducer.Registration.ImplementationType,
instanceProducer.Registration.Lifestyle);
}
public Type ServiceType { get; }
public Type ImplementationType { get; }
public Lifestyle Lifestyle { get; }
}
private readonly Container _underlineContainer;
}
私はExpressionBuildとExpressionBuildingイベントで演奏し、それが組み込みの検証プロセスに参加しているすべての登録をカバーするので、彼らはスレッドの安全性違反(または任意の他のサービスのリストを埋めるために使用することができ気づきました-implementatイオンライフスタイルのカスタムルール違反)を使用して、ビルトイン検証プロセスの終了時に例外を発生させます。 – Eldar