Autofac контравариантность и разрешение открытых универсальных типов
При разрешении всех реализаций универсального типа (с контравариантным T) из autofac я хотел бы получить все возможные контравариантные совпадения. Это работает только при регистрации ContravariantRegistrationSource. Но потом я получаю слишком много экземпляров для открытых универсальных реализаций, потому что при этом дерево наследования дает мне экземпляр для каждого подкласса.
Это может показаться немного абстрактным, поэтому вот 2 модульных теста, которые демонстрируют проблему. Они оба терпят неудачу, но я хотел бы заставить хотя бы одного из них работать:
using Autofac;
using FluentAssertions;
using System.Collections.Generic;
using Xunit;
using Autofac.Features.Variance;
namespace Aiv.Vbr.QueryService.WebApi.Test.AdresMatchTests
{
public class TestAutofacGenerics
{
public interface IGenericInterface<in T> { }
public class GenericImplementation<T> : IGenericInterface<T> { }
public class SpecificImplementation : IGenericInterface<TClass> { }
public class TInterfaceImplementation : IGenericInterface<TInterface> { }
public interface TInterface { }
public class TClass : TInterface { }
[Fact]
public void AutofacShouldAlsoResolveContravariantImplementations()
{
var builder = new ContainerBuilder();
builder.RegisterType<SpecificImplementation>().As<IGenericInterface<TClass>>();
builder.RegisterType<TInterfaceImplementation>().As<IGenericInterface<TInterface>>();
builder.RegisterGeneric(typeof(GenericImplementation<>)).As(typeof(IGenericInterface<>));
var instances = builder.Build().Resolve<IEnumerable<IGenericInterface<TClass>>>();
//This fails: only 2 types get resolved: GenericImplementation<TClass> and SpecificImplementation
//but also expected TInterfaceImplementation
instances.Should().HaveCount(3);
}
[Fact]
public void AutofacShouldOnlyResolveOpenGenericsForSpecifiedClass()
{
var builder = new ContainerBuilder();
builder.RegisterSource(new ContravariantRegistrationSource());
builder.RegisterType<SpecificImplementation>().As<IGenericInterface<TClass>>();
builder.RegisterType<TInterfaceImplementation>().As<IGenericInterface<TInterface>>();
builder.RegisterGeneric(typeof(GenericImplementation<>)).As(typeof(IGenericInterface<>));
var instances = builder.Build().Resolve<IEnumerable<IGenericInterface<TClass>>>();
//This fails: 5 types get resolved: GenericImplementation<TClass>, GenericImplementation<Object>,
// GenericImplementation<TInterface>, SpecificImplementation and TInteraceImplementation
//but did not want GenericImplementation<Object> and GenericImplementation<TInterface>
instances.Should().HaveCount(3);
}
}
}
Проблема описана здесь, и возможное решение предлагается использовать пользовательский ContravariantRegistrationSource, который ограничен, но я не вижу, как это может решить мою проблему. Что я могу сделать?
1 ответ
Вопрос связан с тем, как и
RegisterGeneric
работает
Когда вы решите
GenericImplementation<TClass>
ContravariantRegistrationSource
постараюсь решить
GenericImplementation<TClass>
GenericImplementation<Object>
GenericImplementation<TInterface>
потому что у тебя есть
builder.RegisterGeneric(typeof(GenericImplementation<>))
.As(typeof(IGenericInterface<>));
Autofac вернет регистрацию для каждого из них.
Это ожидаемое поведение, и, к сожалению, нет простого способа его исправить.
У меня была такая же проблема с MediatR и
INotificationHandler
, я закончил тем, что сделал свой собственный
IRegistrationSource
.
/// <summary>
/// Returns contravariant registration source without duplicator target.
///
/// <see cref="ContravariantRegistrationSource" /> returns all contravariant implementation of a type.
/// For example when we resolve IEnumerable<INotificationHandler<SpecificCommand>> it will returns a collection with GenericHandler<SpecificCommand>, GenericHandler<BaseCommand>, SpecificCommandHandler
/// this registration source will first look up for the native registrations and then group registration based on activator limit type.
/// </summary>
/// <remarks>See https://stackoverflow.com/questions/46464944/autofac-contravariance-and-resolving-open-generic-types </remarks>
public class ExplicitContravariantRegistrationSource : IRegistrationSource
{
private readonly IRegistrationSource _source = new ContravariantRegistrationSource();
private readonly Type _type;
public ExplicitContravariantRegistrationSource(Type type)
{
if (type == null)
{
throw new ArgumentNullException(nameof(type));
}
if (!type.IsGenericTypeDefinition)
{
throw new ArgumentException("Type should be a generic type definition", nameof(type));
}
this._type = type;
}
public IEnumerable<IComponentRegistration> RegistrationsFor(
Service service,
Func<Service, IEnumerable<ServiceRegistration>> registrationAccessor)
{
if (service is IServiceWithType st
&& st.ServiceType.IsGenericType
&& this._type == st.ServiceType.GetGenericTypeDefinition())
{
// get all non contravariant registration source
var originalRegistrations = registrationAccessor(service).ToArray();
var components = _source
// retrieve all contravariant registration source
.RegistrationsFor(service, registrationAccessor)
// Group will ensure having only a single registration of a activator limit type
.GroupBy(this.GetTargetTypeDefinitionOrSelf)
// exclude groups if autofac already resolved the same activator limit type
.Where(o => !originalRegistrations.Select(oo => this.GetTargetTypeDefinitionOrSelf(oo.Registration)).Contains(o.Key))
// taking the last is the default behavior for autofac, it can be improved
.Select(o => o.Last());
return components;
}
else
{
return Enumerable.Empty<IComponentRegistration>();
}
}
private Type GetTargetTypeDefinitionOrSelf(IComponentRegistration componentRegistration)
{
return componentRegistration.Target.Activator.LimitType.IsGenericType ?
componentRegistration.Target.Activator.LimitType.GetGenericTypeDefinition()
: componentRegistration.Target.Activator.LimitType;
}
public bool IsAdapterForIndividualComponents => _source.IsAdapterForIndividualComponents;
}
и я использую это так:
builder.RegisterSource(new ExplicitContravariantRegistrationSource(typeof(INotificationHandler<>)));