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

问题描述 投票:0回答:1

首先,设置

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

c# generics dependency-injection casting parametric-polymorphism
1个回答
0
投票

由于您总是在编译时将

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
。可能还有其他解决方案,但很难判断您的情况下最好的解决方案是什么,因为您为此提供的背景太少了。

© www.soinside.com 2019 - 2024. All rights reserved.