在某些特定情况下,用作控制流机制的异常是否有效?

时间:2015-06-26 12:58:33

标签: c# .net exception-handling

我在RationalNumber实现中调整了一些代码。特别是在平等逻辑中,我考虑以下几点:

public bool Equals(RationalNumber other)
{
   if (RationalNumber.IsInfinity(this) ||
       RationalNumber.IsInfinity(other) ||
       RationalNumber.IsNaN(this) ||
       RationalNumber.IsNaN(other))
   {
       return false;
   }

   try
   {
       checked
       {
           return this.numerator * other.Denominator == this.Denominator * other.numerator;
       }
   }
   catch (OverflowException)
   {
       var thisReduced = RationalNumber.GetReducedForm(this);
       var otherReduced = RationalNumber.GetReducedForm(other);
       return (thisReduced.numerator == otherReduced.numerator) && (thisReduced.Denominator == otherReduced.Denominator);
   }
}

正如您所看到的,我使用异常作为流量控制机制。这背后的原因是我不想在每次平等检查中评估两个分数的最大公约数的惩罚。因此,我只决定在最不可能的情况下这样做:一个或两个交叉产品溢出。

这是可接受的做法吗?我一直都读到,异常永远不应该被用作代码的流程机制,但我并没有真正看到另一种实现我想要的方式。

欢迎任何其他方法。

3 个答案:

答案 0 :(得分:2)

  

这背后的原因是我不想在每次平等检查中评估两个分数的最大公约数时会受到惩罚。

这是合理的推理。此代码的总费用为

{probability of fast-path} * {fast-path cost}
+ ((1.0 - {probability of fast-path}) * {slow-path cost})

根据所涉及的三个常数,这将是一个好的或坏的选择。您需要充分了解将在实践中处理的数据。

请注意,异常非常缓慢。我曾经对它们进行基准测试,每个CPU核心每秒10000个,我不确定它们是否会因为涉及内部CLR锁而扩展到多个核心。

也许您可以添加运行时分析。跟踪异常率。如果太高,请关闭优化。

您可能应该记录为什么这样做。

它也不是一个架构问题,因为如果您以后改变主意,可以轻松切换到不同的算法。

作为替代方案,您可以先计算并比较未选中状态。如果结果是"不等于"保证确切的结果是"不等于"。即使发生了溢出。如果许多数字变得不相等,那么这可能是一个例外的免费快速路径。

答案 1 :(得分:1)

通常捕获异常会产生很高的开销,如果你可以对它们做些什么,你应该捕获异常。

在您的情况下,您可以对异常做些什么。在我看来,使用它作为控制流不是问题但是我建议你实现逻辑(检查不同条件以防止异常)然后对两个选项进行基准测试并比较性能,因为通常捕获异常具有高开销但是如果检查以便防止异常需要更多时间,然后处理异常是更好的方法。

由于OP评论而更新(它是一个新的实现,我们没有使用.NET框架的Rational。分子和分母的类型是long

您可以使用更大的类型来防止溢出异常,例如decimalBigInteger

decimal thisNumerator = this.numerator;
decimal thisDenominator = this.numerator;
decimal otherNumerator = other.numerator;
decimal otherDenominator = other.numerator;

checked
{
    return thisNumerator * otherDenominator == thisDenominator * otherNumerator;
}

由于评论而更新:

显示异常开销的简单示例。

const int Iterations = 100000;
var sw = new Stopwatch();
var sum1 = 0;
sw.Start();
for (int i = 0; i < Iterations; i++)
{
    try
    {
        var s = int.Parse("s" + i);
        sum1 += s;
    }
    catch (Exception)
    {
    }
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
Console.WriteLine(sum1);

var sw2 = new Stopwatch();
var sum2 = 0;
sw2.Start();
for (int i = 0; i < Iterations; i++)
{
    try
    {
        int s;
        if (int.TryParse("s" + i, out s))
            sum2 += s;
    }
    catch (Exception)
    {
    }
}
sw2.Stop();
Console.WriteLine(sw2.ElapsedMilliseconds);
Console.WriteLine(sum2);

结果是:处理异常的速度至少慢170倍

  

5123
  0
  30个
  0

答案 2 :(得分:0)

这种方法是在MSDN中引入的。 https://msdn.microsoft.com/en-Us/library/74b4xzyw.aspx

但是捕获异常是一个很高的开销,因为在这段时间内,进程模式会将用户模式更改为内核模式。