首先,设置
interface IRequirement { }
interface ITarget { ICollection<IRequirement> Requirements { get; set; } }
interface IRequirementHandler<TRequirement, TTarget>
where TRequirement : IRequirement
where TTarget : ITarget {}
class AgeMustBeGreaterThanThirtyRequirement : IRequirement {}
class Person : ITarget { int Age { get; set; } }
class PersonAgeMustBeGreaterThanThirtyRequirementHandler :
IRequirementHandler<AgeMustBeGreaterThanThirtyRequirement, Person> {}
// register requirement handler in DI
services.AddSingleton
<IRequirementHandler<AgeMustBeGreaterThanThirtyRequirement, Person>,
PersonAgeMustBeGreaterThanThirtyRequirementHandler>();
基本上我有目标实体、需求和需求处理程序。一个需求可以应用于多个目标实体。需求处理程序处理针对目标的具体需求。
所有 ITarget 实体都有一个集合
IRequirement
interface ITarget { ICollection<IRequirement> Requirements { get; set; } }
我有一个管理器类,它使用 .NET
IServiceProvider
作为服务定位器,来解决每个需求的具体处理程序。
interface IRequirementHandlerLocator
{
IRequirementHandler<TRequirement, TTarget> GetHandler<TRequirement, TTarget>()
where TRequirement : IRequirement
where TTarget : ITarget
}
这是我意识到我对泛型和类型主题有多么无知的地方。我无法按原样实现
IRequirementHandlerLocator
,所以我必须将其更改为
interface IRequirementHandlerLocator
{
IRequirementHandler<TRequirement, TTarget> GetHandler<TRequirement, TTarget>(TRequirement requirement,
TTarget target) where TRequirement : IRequirement
where TTarget : ITarget
}
并以这种方式实现它
class RequirementHandlerLocator : IRequirementHandlerLocator
{
IRequirementHandler<TRequirement, TTarget> GetHandler<TRequirement, TTarget>(TRequirement requirement,
TTarget target) where TRequirement : IRequirement
where TTarget : ITarget
{
return _serviceProvider.GetRequiredService<IRequirementHandler<TRequirement, TTarget>>();
}
}
我心想,这样当我调用
GetHandler()
时就会推断出泛型类型。例如:
ITaskRequirementHandlerLocator _requirementHandlerLocator;
bool DoesTargetSatisfyRequirements(ITarget target)
{
foreach (var requirement in target.Requirements)
{
var handler = _requirementHandlerLocator.GetHandler(requirement, target);
if (!handler.QualifyAgainst(target))
return false;
}
}
那些对这个主题有深入了解的人都知道这失败了。因为虽然
TRequirement
实际运行时类型是 AgeMustBeGreaterThanThirtyRequirement
,但它被解析为 IRequirement
,完整解析的类型 => IRequirementHandler<IRequirement, ITarget>
。 DI 的注册是
// register requirement handler in DI
services.AddSingleton
<IRequirementHandler<AgeMustBeGreaterThanThirtyRequirement, Person>,
PersonAgeMustBeGreaterThanThirtyRequirementHandler>();
所以DI容器没有找到类型 我了解到(并且我假设)泛型捕获/推断给定类型而不是实际的运行时类型。因为我不知道编译时的实际类型=>
我需要你的帮助来解决这个谜题,也许还可以为我和像我这样的人添加一些信息,以更深入地理解这个问题。
编辑: 我向微软的 bing chatGPT 之类的服务询问了此事。它建议
var handlerType = typeof(ITaskRequirementHandler<,>)
.MakeGenericType(typeof(TRequirement), typeof(TTarget));
//success. DI resolved the handler.
var handler = _serviceProvider.GetRequiredService(handlerType);
//failure. casting failed.
return (IRequirementHandler<TRequirement, TTarget>) handler;
尽管建议解决了正确的处理程序类型并且 DI 返回了处理程序。但我无法将其转换为方法签名。
由于您总是在编译时将
IRequirement
和 ITarget
作为泛型类型参数传递,因此 IRequirementHandlerLocator
的泛型类型参数变得多余。因此,我建议将该抽象更改为以下内容:
interface IRequirementHandlerLocator
{
IRequirementHandler<IRequirement, ITarget> GetHandler(
IRequirement requirement, ITarget target);
}
在这种情况下,您的
RequirementHandlerLocator
应变为:
class RequirementHandlerLocator : IRequirementHandlerLocator
{
IRequirementHandler<IRequirement, ITarget> GetHandler(
IRequirement requirement, ITarget target)
{
var handlerType = typeof(IRequirementHandler<,>)
.MakeGenericType(requirement.GetType(), target.GetType());
object handler = _serviceProvider.GetRequiredService(handlerType);
return (IRequirementHandler<IRequirement, ITarget>)handler;
}
}
这里只有一个问题,那就是
RequirementHandlerLocator
中的最后一次转换将导致运行时异常,指出:
无法将“PersonAgeMustBeGreaterThanThirtyRequirementHandler”类型的对象转换为“IRequirementHandler`2[IRequirement,ITarget]”类型。”
这是因为界面没有变体,并且
IRequirementHandler<IRequirement, ITarget>
与 IRequirementHandler<AgeMustBeGreaterThanThirtyRequirement, Person>
不同。为了使它们可以互换,您必须使 IRequirementHandler<R, T>
协变。换句话说,您必须将 out
关键字添加到泛型类型约束中。换句话说:
interface IRequirementHandler<out TRequirement, out TTarget>
where TRequirement : IRequirement
where TTarget : ITarget
{
}
但是,只有当两个泛型类型参数仅用作输出参数时,这种更改才会起作用。你的问题没有提到这一点,但情况可能并非如此。这意味着无论你如何尝试,你的代码都无法正常工作。为什么会出现这种情况,在这里解释起来太费力了,但是 Eric Lippert 有关于泛型类型、方差、协方差和逆变的精彩介绍博客文章。
解决这个问题的方法是让
IRequirementHandler<R, T>
从非泛型基类型继承,例如:
interface IRequirementHandler { }
interface IRequirementHandler<out TRequirement, out TTarget>
where TRequirement : IRequirement
where TTarget : ITarget
: IRequirementHandler
{
}
这样
IRequirementHandlerLocator
就可以返回IRequirementHandler
。可能还有其他解决方案,但很难判断您的情况下最好的解决方案是什么,因为您为此提供的背景太少了。