【问题标题】:Making Handlers Generic使处理程序通用
【发布时间】:2022-01-20 06:03:45
【问题描述】:

所以我正在编写一个 C# System.Commandline 应用程序,并且我注意到我的方法都遵循类似的结构 - 每个 Handler 类都有一个公共方法 RunHandlerAndReturnExitCode,采用一组不同的选项,我已经封装到一个类中作为参数传递。如下:

public class FirstHandler
{
    public int RunHandlerAndReturnExitCode(FirstOptions options) { }
}

public class SecondHandler
{
    public int RunHandlerAndReturnExitCode(SecondOptions options) { }
}

等等。我尝试创建一个 OptionsBase 抽象类,并让我的其他 Options 类从它继承,然后创建了一个处理程序接口,如下所示:

internal interface IHandler
    {
        int RunHandlerAndReturnExitCode<T>(T options) where T : OptionsBase;
    }

处理程序看起来像:

public class FirstHandler : IHandler
{
    public int RunHandlerAndReturnExitCode<FirstOptions>(FirstOptions options) { }
}

编辑:我还有从 OptionsBase 继承的 Options 类:

public class FirstOptions : OptionsBase
{
    public string FirstProperty { get; set; }
}

还有 OptionsBase 类:

public abstract class OptionsBase { }

但这会返回错误“类型参数 'FirstOptions' 的约束必须与类型参数 T 的约束匹配。(请考虑改用显式接口实现)。

我哪里错了?这甚至是正确的方法吗?

【问题讨论】:

  • FirstOptions 必须继承 OptionsBase
  • 抱歉,确实会增加我原来的问题
  • 似乎您还必须在方法上添加约束。 public int RunHandlerAndReturnExitCode&lt;FirstOptions&gt;(FirstOptions options) where FirstOptions : OptionsBase { return 0; }
  • 看你的OptionsBase是什么,放在描述里
  • @TornikeCholadze 已经添加了它

标签: c# .net inheritance object-oriented-analysis


【解决方案1】:

根据您要执行的操作,您可以按照以下方式进行操作。

接口应该设置通用类型,即。 IHandler&lt;T&gt; where T : OptionsBase,然后分别实现FirstHandlerSecondHandler

public class OptionsBase
{

}

public class FirstOptions : OptionsBase
{

}

public class SecondOptions : OptionsBase
{

}

internal interface IHandler<T> where T : OptionsBase
{
    int RunHandlerAndReturnExitCode(T options);
}

public class FirstHandler : IHandler<FirstOptions>
{
    public int RunHandlerAndReturnExitCode(FirstOptions options)
    {
        throw new NotImplementedException();
    }
}    

public class SecondHandler : IHandler<SecondOptions>
{
    public int RunHandlerAndReturnExitCode(SecondOptions options)
    {
        throw new NotImplementedException();
    }
}

这使您能够单独测试每个处理程序,引入基本抽象类,并使用 DependencyInjection 作为选项:如以下示例:

public abstract class AbstracHandler<T> : IHandler<T> where T : OptionsBase
{
    protected readonly T _options;

    public AbstracHandler(T options)
    {
        _options = options;
    }
    public abstract int RunHandlerAndReturnExitCode(T options);
}

public class FirstHandler : AbstracHandler<FirstOptions>
{
    public FirstHandler(FirstOptions options) : base(options)
    {

    }

    public override int RunHandlerAndReturnExitCode(FirstOptions options)
    {
        throw new NotImplementedException();
    }
}

【讨论】:

  • 我试试看!你甚至已经提前回答了我下一个问题的一部分,尽管我还有一个后续问题:我将如何在依赖注入中注册处理程序?我只是在使用默认的 .NET 依赖注入系统
  • 不太确定如何注册它们,这完全取决于您的应用程序的工作方式。选项是从配置文件中读取的吗?还是创建运行时?这将改变创建对象的方式。您还可以选择工厂类 - 将选项传递给方法以获取正确的处理程序。
  • 这些选项是从命令行上的 args 解析的,所以它们是在注入所有依赖项之后很久才创建的(所以我从我到目前为止所尝试的理解是,采用 OptionsBase 的构造函数赢了在这种情况下不工作?)
  • 所以我可以这样做:services.AddScoped, FirstHandler>();但是我需要手动注册每种类型,这肯定不对吗?
【解决方案2】:

您正在函数级别设置抽象和约束,这意味着任何实现该接口的类都将实现带有约束的抽象方法。

public class Program
{
    public static void Main()
    {
        new GenericHandler<FirstOptions>().RunHandlerAndReturnExitCode(new FirstOptions
        {
            FirstProperty = "FirstOptionProp",
            SomeBaseOptionOne = 1,
            SomeBaseOptionTwo = 2,
        });
        new FirstHandler().RunHandlerAndReturnExitCode(new FirstOptions
        {
            FirstProperty = "FirstOptionProp",
            SomeBaseOptionOne = 1,
            SomeBaseOptionTwo = 2,
        }); ;
    }
}

public abstract class OptionsBase
{
    public int SomeBaseOptionOne { get; set; }
    public int SomeBaseOptionTwo { get; set; }
}

public class FirstOptions : OptionsBase
{
    public string FirstProperty { get; set; }
}

internal interface IHandler<T>
    where T : OptionsBase
{
    int RunHandlerAndReturnExitCode(T options);
}

public class FirstHandler : IHandler<FirstOptions>
{
    public int RunHandlerAndReturnExitCode(FirstOptions options)
    {
        return options.SomeBaseOptionTwo;
    }
}

public class GenericHandler<T> : IHandler<T>
{
    public int RunHandlerAndReturnExitCode(T options)
    {
        //TODO: do some generic imlementation

        return 0;
    }
}

您可以在服务中将其注册为:

services.AddScoped(typeof(IHandler<FirstOptions>), typeof(FirstHandler))
services.AddScoped(typeof(IHandler<>), typeof(GenericHandler<>))

【讨论】:

  • 无法让第一个变体在这里工作,但我意识到我需要创建一个新变量,例如 opts = options as FirstOptions,现在它可以工作了!
  • @GaryMacGregor-Manzi 我编辑了现在检查一下
  • 有道理,谢谢。然后我不需要在 DI 中注册每个不同的处理程序吗?我最终会得到很多:services.AddScoped(typeof(IHandler), typeof(FirstHandler)) services.AddScoped(typeof(IHandler), typeof(SecondHandler)) 等等?
  • 你需要一个一个地注册每个处理程序,但是对于每个处理程序,你可以有不同的选项,这里的泛型类型是选项对象,而不是处理程序本身
  • 如果你想为不同的选项使用通用处理程序,它的实现略有不同
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-30
  • 2013-06-12
  • 2023-03-07
  • 1970-01-01
相关资源
最近更新 更多