Autofac 逆变和解析开放泛型类型

Autofac contravariance and resolving open generic types

从 autofac 解析泛型类型(带有逆变 T)的所有实现时,我想获得所有可能的逆变匹配。这仅在注册 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);
        }
    }    
}

问题已得到描述 here,建议的可能解决方案是使用范围限定的自定义 ContravariantRegistrationSource,但我看不出这如何解决我的问题. 我能做什么?

此问题与 ContravariantRegistrationSourceRegisterGeneric 的工作方式有关

当你解决 GenericImplementation<TClass> ContravariantRegistrationSource 将尝试解决

GenericImplementation<TClass>
GenericImplementation<Object>
GenericImplementation<TInterface>

因为你有

builder.RegisterGeneric(typeof(GenericImplementation<>))
       .As(typeof(IGenericInterface<>));

Autofac 将为他们每个人returns 注册。

这是预期的行为,不幸的是没有简单的方法来修复它。

我在 MediatRINotificationHandler 上遇到了同样的问题,最后我自己做了 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  </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<>)));