解析泛型参数的实际运行时类型,而不是推断类型

resolving actual runtime type of generic parameter rather than inferred type

提问人:mhDuke 提问时间:10/28/2023 最后编辑:StevenmhDuke 更新时间:10/30/2023 访问量:53

问:

一、设置

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 中的注册是TRequirementAgeMustBeGreaterThanThirtyRequirementIRequirementIRequirementHandler<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 返回了处理程序。但我无法将其转换为方法签名。

C# 泛型依赖 关系注入 强制转换 参数多态性

评论

0赞 Joel Coehoorn 10/28/2023
.Net 中的泛型类型仍会在编译时解析,而不是在运行时解析。
0赞 mhDuke 10/28/2023
@JoelCoehoorn我明白了。但是有一个我盲目/无知的解决方案。库制作者到处都在这样做,但我还没有理解他们的代码。
2赞 jmcilhinney 10/28/2023
如果您尝试确定泛型方法中的实际类型,那么您可能做错了。泛型的全部意义在于它们是泛型的,即它们适用于任何可以传入的类型。如果你正在编写特定于类型的代码,那么这就违背了泛型的目的。
0赞 Olivier Jacot-Descombes 10/28/2023
如果您在运行时收到错误的类型,这可能是 DI 容器问题,而不是泛型问题。
1赞 mhDuke 10/31/2023
@Steven是的,每个实例都有不同的要求集。ITarget 实际上是一个应用程序(就像您申请驾驶执照时一样),它由带有步骤的工作流驱动,其中每个步骤都有一组要完成的要求。

答:

1赞 Steven 10/30/2023 #1

由于您总是在编译时作为泛型类型参数传递,因此泛型类型参数变得多余。因此,我建议将该抽象更改为以下内容:IRequirementITargetIRequirementHandlerLocator

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
{
}

这种方式可以返回。可能还有其他解决方案,但就您的情况而言,最好的解决方案是什么很难说,因为您为此提供的上下文太少了。IRequirementHandlerLocatorIRequirementHandler

评论

0赞 mhDuke 10/31/2023
是的。我确实需要泛型类型 arugments 作为输出参数。是的,我没有提到这一点,因为我对待一般论点,就好像它们会理解我的意图一样。我陷入了解决领域功能的泥潭。
0赞 mhDuke 10/31/2023
这正是我最终所做的。我忽略了泛型参数,并按照您的建议依赖于方法参数类型。并为处理程序使用了非泛型基础。但我对结果并不满意,我相信这个功能需要更多的自由。>您必须使 IRequirementHandler<R, T> 协变。我相信这正是我获得额外自由所需要的。我需要回顾方差和协方差的主题。谢谢你为我指明了正确的方向 <3