提问人:mhDuke 提问时间:10/28/2023 最后编辑:StevenmhDuke 更新时间:10/30/2023 访问量:53
解析泛型参数的实际运行时类型,而不是推断类型
resolving actual runtime type of generic parameter rather than inferred type
问:
一、设置
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;
}
}
那些对这个话题有充分了解的人都知道这失败了。因为尽管实际的运行时类型是,但它被解析为 ,完全解析类型 => 。在 DI 中的注册是TRequirement
AgeMustBeGreaterThanThirtyRequirement
IRequirement
IRequirementHandler<IRequirement, ITarget>
// 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
评论