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,但我看不出这如何解决我的问题.
我能做什么?
此问题与 ContravariantRegistrationSource
和 RegisterGeneric
的工作方式有关
当你解决 GenericImplementation<TClass>
ContravariantRegistrationSource
将尝试解决
GenericImplementation<TClass>
GenericImplementation<Object>
GenericImplementation<TInterface>
因为你有
builder.RegisterGeneric(typeof(GenericImplementation<>))
.As(typeof(IGenericInterface<>));
Autofac 将为他们每个人returns 注册。
这是预期的行为,不幸的是没有简单的方法来修复它。
我在 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 </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<>)));
从 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,但我看不出这如何解决我的问题. 我能做什么?
此问题与 ContravariantRegistrationSource
和 RegisterGeneric
的工作方式有关
当你解决 GenericImplementation<TClass>
ContravariantRegistrationSource
将尝试解决
GenericImplementation<TClass>
GenericImplementation<Object>
GenericImplementation<TInterface>
因为你有
builder.RegisterGeneric(typeof(GenericImplementation<>))
.As(typeof(IGenericInterface<>));
Autofac 将为他们每个人returns 注册。
这是预期的行为,不幸的是没有简单的方法来修复它。
我在 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 </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<>)));