使处理程序通用

Posted

技术标签:

【中文标题】使处理程序通用【英文标题】: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 已经添加了它 【参考方案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)) 等等? 你需要一个一个地注册每个处理程序,但是对于每个处理程序,你可以有不同的选项,这里的泛型类型是选项对象,而不是处理程序本身 如果你想为不同的选项使用通用处理程序,它的实现略有不同

以上是关于使处理程序通用的主要内容,如果未能解决你的问题,请参考以下文章

概率软逻辑(PSL,Probabilistic soft logic)通用(可处理中文)版本

在带弹簧休息的全局异常处理程序中使用通用异常类处理程序是一种好习惯吗?

IIS 7.0 503 错误与实现 IHttpAsyncHandler 的通用处理程序 (.ashx)

如何从通用处理程序访问 application.get

具有通用处理程序和查询的 Mediatr

ASP.NET 通用处理程序和会话