【问题标题】:SingleOrDefault exception handlingSingleOrDefault 异常处理
【发布时间】:2014-07-03 16:44:07
【问题描述】:

我有一个示例代码,它调用 SingleOrDefault 方法 3 次,如果任何序列有多个匹配元素,则记录异常。

如果我想检查这段代码的哪一部分抛出异常,问题就开始了。

是否可以从此异常中获取一些有用的信息,例如谓词参数或集合类型以进行更详细的跟踪?

像这样 - 序列包含多个匹配元素。集合 IEnumrable|ParamType|参数 {谓词参数 toString()}

 public void GetSingleOrDefaultTest(){

    try{

        var user = Users.SingleOrDefault(e => e.Id == 1);

        var profile = UserProfiles.SingleOrDefault(e => e.Id == 1);

        var profile2 = UserProfiles.SingleOrDefault(e => e.Id == 2);


    } catch(InvalidOperationException ex){
        Log(ex);
    }

}

【问题讨论】:

  • 你一开始就做错了。不要依赖异常处理来控制流。你会不小心发现真正的错误并隐藏它们,以及其他问题。
  • @usr 仅从这个 sn-p 中我并不清楚它是否用于控制流
  • @BenAaronson 如果异常是执行的预期部分(并且可以避免),那么它们将用于控制流。这就是我的定义。
  • @usr 是的,我只是不确定这种情况是否如此。为什么具有相同 ID 的多个用户会成为预期的执行部分?这看起来像标准的“检查我的数据没有以某种方式进入奇怪的意外状态”代码

标签: c# .net linq


【解决方案1】:

如果您想知道哪个语句发出错误,您必须单独检查它们。在每个 SingleOrDefault 调用中捕获 InvalidOperationException 并将其包装在一个新的异常中,您可以在其中填写其他信息。

try
{
    User user;
    UserProfile profile;
    UserProfile profile2;

    try
    {
        user = Users.SingleOrDefault(e => e.Id == 1);
    }
    catch (InvalidOperationException ex)
    {
        throw new InvalidOperationException("User lookup for Id = 1 failed", ex);
    }

    try
    {
        profile = UserProfiles.SingleOrDefault(e => e.Id == 1);
    }
    catch (InvalidOperationException ex)
    {
        throw new InvalidOperationException("User profile lookup for Id = 1 failed", ex);
    }

    try
    {
        profile2 = UserProfiles.SingleOrDefault(e => e.Id == 2);
    }
    catch (InvalidOperationException ex)
    {
        throw new InvalidOperationException("User profile lookup for Id = 2 failed", ex);
    }

    // work with user, profile and profile2
}
catch(InvalidOperationException ex)
{
    Log(ex);
}

编辑:

您也可以通过以下方式封装单个尝试捕获

private static T GetSingleOrDefault<T>(IEnumerable<T> collection, Expression<Func<T, bool>> predicate)
{
    try
    {
        return collection.SingleOrDefault(predicate.Compile());
    }
    catch (InvalidOperationException e)
    {
        var message = string.Format(
            "{0} (Collection: {1}, param: {2})",
            e.Message,
            collection.GetType(),
            predicate);

        throw new InvalidOperationException(message);
    }
}

让你的代码看起来像

try
{
    var user = GetSingleOrDefault(Users, e => e.Id == 1);

    var profile = GetSingleOrDefault(UserProfiles, e => e.Id == 1);

    var profile2 = GetSingleOrDefault(UserProfiles, e => e.Id == 2);

    // work with user, profile and profile2
}
catch(InvalidOperationException ex)
{
    Log(ex);
}

这会产生类似的消息

System.InvalidOperationException:序列包含多个匹配元素(集合:IEnumerable`1[User],参数:e => e.Id == 1)

【讨论】:

    【解决方案2】:

    当您使用 SingleOrDefault 时,您清楚地声明查询最多只能产生一个结果。另一方面,当使用 FirstOrDefault 时,查询可以返回任意数量的结果,但您声明您只想要第一个。

    我个人发现语义非常不同,根据预期结果使用适当的语义可以提高可读性。

    Reference

    【讨论】:

    • 这是真的,但在这种情况下我认为SingleOrDefault是他真正想要的
    【解决方案3】:

    没有理由使用 SingelOrDefault。我会将其重构为:

    var user = Users.Count(e => e.Id == 1);
    var profile = UserProfiles.Count(e => e.Id == 1);
    var profile2 = UserProfiles.Count(e => e.Id == 2);
    
    if(user + profile + profile2 != 3){
      Log("more than one");
    }
    

    这基本相同,但不是异常驱动的。而且我在您的问题中没有看到使用异常驱动编程的理由。

    【讨论】:

      【解决方案4】:

      这就是日志进入场景的地方(NLog 使用示例,可​​从 NuGet 获得):

      public void GetSingleOrDefaultTest()
      {
          try
          {
              Logger.Debug("Getting user with id = {0}", 1);
              var user = Users.SingleOrDefault(e => e.Id == 1);
      
              Logger.Debug("Getting user profile with id = {0}", 1);
              var profile = UserProfiles.SingleOrDefault(e => e.Id == 1);
      
              Logger.Debug("Getting user profile with id = {0}", 2);
              var profile2 = UserProfiles.SingleOrDefault(e => e.Id == 2);
          } 
          catch(Exception ex)
          {
              Logger.ErrorException("Failed getting single or default", ex);
          }
      }
      

      看一眼日志文件就会告诉你哪个语句失败了:

      2014-05-26 12:04:48.8655 DEBUG Getting user with id = 1
      2014-05-26 12:04:48.8815 DEBUG Getting user profile with id = 1
      2014-05-26 12:04:48.8815 DEBUG Getting user profile with id = 2
      2014-05-26 12:04:48.8815 ERROR Failed getting single or default
      Sequence contains more than one matching element
      

      【讨论】:

        【解决方案5】:

        在我回答之前:请注意在此处使用异常处理的危险。在这个问题的 cmets 中,我指出了一些问题。最后,您可能会通过记录误导性信息恶化您的日志输出。

        不要依赖异常处理来控制流。你会不小心发现真正的错误并隐藏它们,以及其他问题。

        异常也有非常糟糕的表现。

        使用异常处理来诊断特定问题非常容易出错。让我们看看我们是否可以在不使用它的情况下逃脱。

        public static TSource SingleOrDefaultWithDiagnostics<TSource>(
          this IEnumerable<TSource> source,
          Func<TSource, bool> predicate,
          string failureMessage) {
        
            using (IEnumerator<TSource> enumerator = source.Where(predicate).GetEnumerator())
            {
                if (!enumerator.MoveNext())
                {
                    return default(TSource);
                }
                TSource current = enumerator.Current;
                if (!enumerator.MoveNext())
                {
                    return current;
                }
                else throw new SingleElementException(failureMessage);
            }
        }
        

        我采用了一些 BCL 代码并更改了错误的抛出方式。

        在您正在寻找的情况下,这会输出一个有意义的异常。您可以附加自定义诊断消息。

         var user = Users.SingleOrDefaultWithDiagnostics(e => e.Id == 1, "User with Id 1");
         var profile = UserProfiles.SingleOrDefaultWithDiagnostics(e => e.Id == 1, "...");
         var profile2 = UserProfiles.SingleOrDefaultWithDiagnostics(e => e.Id == 2, "...");
        

        【讨论】:

        • 你能解释一下这个方法的要点吗?看起来你刚刚重新实现了SingleOrDefault
        • @BenAaronson 您现在可以从异常中确切知道发生了什么。原版的情况并非如此,它会引发无差别的 IOE。
        • SingleOrDefault 的文档说,如果“源包含多个元素”,它将引发 IOE。这和你的一样,只是你使用了不同的异常类型。
        • 谓词也可以抛出,或者可能有一些周围的代码也可以抛出。
        • 但在这种情况下,这些都不正确(是的,好吧,Id 可能有一些奇怪的get 逻辑,但几乎可以肯定没有)。作为这个问题的答案,这是一个非常令人困惑的建议。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多