try / catch vs. if / then / else - 特定案例

时间:2017-01-23 19:15:36

标签: c# if-statement exception-handling try-catch control-structure

在每个人都支持代码正确之前,我意识到通常正确的做事方法是不使用try / catch来控制流程。然而,这是一个有点边缘的情况,我想在其他人在这种情况下会做些什么。

这是示例代码,而不是实际代码,但它应该得到重点。如果有人想让我为这些类型定义一个词典或一些模拟类,请告诉我,我会。

public bool IsSomethingTrue1(string aString)
{
    if (aDictionary.ContainsKey(aString)) //If the key exists..
    {
        var aStringField = aDictionary[aString].Field; //Get the field from the value.
        if (aStringField != null) //If the field isn't null..
        {
            return aStringField.SubField != "someValue"; //Return whether a subfield isn't equal to a specific value.
        }
    }
    return false; //If the key isn't found or the field is null, return false.
}

public bool IsSomethingTrue2(string aString)
{
    try
    {
        return aDictionary[aString].Field.SubField != "someValue"; //Return whether the subfield isn't equal to a specific value.
    }
    catch (KeyNotFoundException) //The key wasn't in the dictionary..
    {
        return false;
    }
    catch (NullReferenceException) //The field (or the dictionary, if it is dynamically assigned rather than hardcoded) was null..
    {
        return false;
    }
}

因此,在上面的示例中,第一个方法检查字典是否包含密钥,如果是,则检查字段是否为空,如果子值不等于,则返回true特定值,否则返回false。这避免了try / catch,但每次访问代码时都会检查字典以查看密钥是否存在(假设没有引擎盖缓存/等等 - 让我知道是否有任何请求) ,然后检查一个字段是否为空,然后检查我们关心的实际检查。

在第二种方法中,我们使用try / catch并避免使用多层控制逻辑。我们马上就到了#34;并尝试访问有问题的值,如果它以两种已知方式之一失败,它将返回false。如果代码成功执行,则可能返回true或false(与上面相同)。

这两种方法都可以完成工作,但我的理解是,如果在大多数情况下没有出错,第一种方法平均会更慢。第一种方法必须每次检查所有内容,其中第二种方法只需处理出现问题的情况。在堆栈上会有一个额外的空间用于异常,以及跳转到不同的catch块等的一些指令。这应该都不会影响常规情况下的性能,除了那个堆栈变量之外没有任何错误,但是,如果我正确理解了我在这里读过的内容: Do try/catch blocks hurt performance when exceptions are not thrown?

当然,通过这个确切的示例,差异可以忽略不计 - 但是,想象一下复杂的if / then / else树中的大量检查与具有失败条件列表的try / catch相比的复杂场景。我意识到是的,这种代码总是可以分解成更小的位或者重构,但是为了论证,让我们说除了控制流之外它不能被改变(有些情况)改变实际逻辑需要重新验证科学算法,例如,这是昂贵/缓慢的,需要最小化。

教科书我意识到答案是使用第一种方法,但在某些情况下,第二种方法可能显着更快。

同样,我知道这有点迂腐,但我真的很想确保我在其重要的地方编写最有效的代码,同时保持一切可读和可维护(并且评论很好)。在此先感谢您就此事提供意见!

2 个答案:

答案 0 :(得分:3)

Exception应该用于例外案例(例如,当出现问题时:找不到文件,连接断开等)。它们非常(堆栈跟踪消耗时间)。在你的实现中,关键是一个非常例程的情况:

    public bool IsSomethingTrue2(string aString) {
      if (null == aString)
        return false; 

      MyType value;

      if (!aDictionary.TryGetValue(aString, out value))
        return false;

      return (null == value) 
        ? false 
        : null == value.Field 
           ? false
           : value.Field.SubField == "someValue"; 
    }

另一个问题是,除非例程有 bug ,否则不应抛出KeyNotFoundExceptionNullReferenceException。调试看起来很困难,请不要把它变成噩梦。

答案 1 :(得分:1)

您似乎同意不使用boneheaded exceptions来控制程序流是正确的事情

尽管如此,您仍然更喜欢try-catch解决方案,因为它是一种简洁且相对清晰的方式,并且由于缺少空/存在检查而且可能更快,并且异常抛出和处理可能不是那么糟糕{ {3}}

这是一个很好的推理,但我们应该更加小心的时刻:

性能。

任何与绩效相关的事情都只有一件事(相对)可靠 - 基准。 衡量一切

我们不知道dictionary中确切的数据模式。如果在物业钻探过程中没有任何东西可以抛出异常,那么你可能会有更好的例外。

但实际的空比较/ TryGetValue方法的成本与异常抛出相比微不足道。

再次 - 根据示例数据对其进行测量,考虑此代码执行的频率,然后再对可接受/不可接受的性能做出任何决定。

可读性

没有人会认为此代码比具有大量if s的代码短。用它犯下任何错误要困难得多。

但是在所有这些可读性背后​​隐藏着一个谬误 - 这样的try-catch代码并不完全等同于原始代码。

正确性/等效。

为什么不完全等效

  1. 由于使用的字典可能不是Dictionary<String, T>,而是某些可能存在缺陷的自定义IDictionary,因此在内部计算过程中会导致NullReferenceException
  2. 或者存储的T对象可能是具有内部对象的代理对象,在某些情况下开始初始化为null,导致NullReferenceException中的T.SomeProp
  3. 或者T可以是另一个Dictionary中某个对象的代理,但它实际上不存在,因此KeyNotFoundException
  4. 在所有这些情况下,我们都会忽略潜在的缺陷。

    你可能会说,在你的特定情况下不会发生 performance-wise 吗?也许,或许不是。您可能决定接受这样的风险,但这种情况并非如此,但这种情况并非不可能,因为系统会发生变化,而这些代码则不然。

    总而言之,我宁愿坚持第一个noexcept解决方案。它有点长,但它完全符合它的说法,在任何情况下都不会有明显更差的性能,并且不会无缘无故地抛出异常。