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&lt;INotificationHandler&lt;SpecificCommand&gt;&gt; it will returns a collection with GenericHandler&lt;SpecificCommand&gt;, GenericHandler&lt;BaseCommand&gt;, 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<>)));
Другие вопросы по тегам