【问题标题】:LINQ method chaining and granular error handlingLINQ 方法链接和细粒度错误处理
【发布时间】:2012-06-07 10:15:23
【问题描述】:

我有一个方法可以通过方法链很好地编写:

return viewer.ServerReport.GetParameters()
    .Single(p => p.Name == Convention.Ssrs.RegionParamName)
    .ValidValues
    .Select(v => v.Value);

但是,我希望能够在每个点进行一些检查,因为如果任何链接方法返回意外结果,我希望提供有用的诊断信息。

要实现这一点,我需要断开所有链接,并在每个调用之后添加一个 if 块。它使代码的可读性大大降低。

理想情况下,我希望能够编织一些链式方法调用,这将允许我在每个点处理意外结果(例如,如果第一个方法返回一个空集合,则抛出一个有意义的异常,例如 new ConventionException("The report contains no parameter"))。任何人都可以提出一个简单的方法来实现这样的事情吗?

编辑:

这是使用@JeffreyZhao 的答案的结果:

return viewer.ServerReport.GetParameters()
    .Assert(result => result.Any(), "The report contains no parameter")
    .SingleOrDefault(p => p.Name == Convention.Ssrs.RegionParamName)
    .Assert(result => result != null, "The report does not contain a region parameter")
    .ValidValues
    .Select(v => v.Value)
    .Assert(result => result.Any(), "The region parameter in the report does not contain any valid value");

【问题讨论】:

    标签: c# linq chaining method-chaining


    【解决方案1】:

    也许你可以使用这种方法。

    static T Check<T>(this T value)
    {
        if (...) throw ...;
    
        return value;
    }
    

    然后:

    xxx.Single(...).Check().Select(...).Check()...
    

    更新:

    你甚至可以:

    static T Validate<T>(this T value, Func<T, bool> validate, string errorMessage)
    {
        if (!validate(value)) 
            throw new ValidationFailedException(errorMessage);
    
        return value;
    }
    

    然后:

    xxxx.Single()
        .Validate(v => v > 0, "Must be greater than zero")
        .NextStep()
        .Validate(...);
    

    【讨论】:

    • 这看起来不错。鉴于错误检查的性质,我怀疑可能不需要通用方法(即错误检查的性质通常特定于给定类型)。拥有一个扩展方法的一般方法,它只返回未修改的输入,这正是我们所需要的。
    • 你是对的。我添加了一个更通用的 Validate 方法,在这种情况下需要泛型。
    • 不错。我希望我可以再次为那个漂亮的小Validate 功能 +1。 :)
    • 完美,谢谢@JeffreyZhao!我采用了您的方法并将其重命名为 Assert。我现在认为理想的解决方案是向现有的 LINQ 方法添加重载,因为这将允许我在异常中使用输入(实际上,对诊断有用的信息是这些方法的输入)。
    【解决方案2】:

    您可以使用局部变量轻松地将流程拆分为单独的步骤:

    var result1 = viewer.ServerReport.GetParameters();
    var result2 = result1.Single(p => p.Name == Convention.Ssrs.RegionParamName);
    var result3 = result2.ValidValues;
    var result4 = result3.Select(v => v.Value);
    return result4;
    

    现在您可以在步骤之间进行任何您想要的检查。

    但是请注意,有些结果实际上并没有做任何工作。例如,最后一步不会产生一个列表作为结果,它会产生一个从ValidValues 读取的可枚举,因此当您使用结果时,该步骤中的任何错误都会发生,而不是在此方法中。您可能希望在某些步骤的末尾添加.ToList() 以实现结果。

    【讨论】:

      【解决方案3】:

      考虑使用Code Contracts(例如,将后置条件添加到ServerReport.GetParameters 以确保方法不会返回空集合)。它们允许以更优雅的方式做你想做的事,而不是编写自己的检查逻辑。

      class ReportParameter { }
      class ServerReport
      {
          public ReportParameter[] GetParameters()
          {
              Contract.Ensures(Contract.Result<ReportParameter[]>() != null && Contract.Result<ReportParameter[]>().Length > 0,
                  Resource1.Oops);
      
              // here's some logic to build parameters array...
              return new ReportParameter[0];
          }
      }
      

      用法:

      // Oops! I need at least one parameter!
      var parameters = new ServerReport().GetParameters();
      

      【讨论】:

      • 这听起来很理想。但在我的特殊情况下,数据需要符合某些预期,因此静态分析无济于事(我意识到这也提供了运行时检查,但我不确定采用这种新方法和使用新工具是否有必要鉴于此)。当条件不满足时,我可以注入自己的逻辑吗?我真的需要更多地研究这个!
      • 我的意思是允许我提供有用的特定异常而不是通用异常类型的逻辑,从而使我的调用代码更容易处理可恢复的错误。
      • @Clafou,你想注入什么逻辑之王?违反合同会导致ContractException。你可以处理它。
      • 听起来不错!可能还有其他需要注入逻辑的要求,例如在不满足某个条件时执行一些特殊的日志记录。这超出了我的要求!
      • @Clafou,我在答案中添加了小样本。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多