是否可以使用返回值和异常更改此代码:
public Foo Bar(Bar b)
{
if(b.Success)
{
return b;
}
else
{
throw n.Exception;
}
}
,这会导致成功和失败的单独例外
public Foo Bar(Bar b)
{
throw b.Success ? new BarException(b) : new FooException();
}
try
{
Bar(b)
}
catch(BarException bex)
{
return ex.Bar;
}
catch(FooException fex)
{
Console.WriteLine(fex.Message);
}
答案 0 :(得分:23)
抛出异常肯定比返回值更昂贵。但就原始成本而言,很难说异常是多么昂贵。
在决定返回值与异常时,您应该始终考虑以下规则。
仅针对特殊情况使用例外
它们不应该用于一般控制流程。
答案 1 :(得分:20)
使用下面的代码,测试显示,没有异常的call + return每次迭代大约需要1.6微秒,而异常(throw plus catch)每次增加 4000 微秒。(!)< / p>
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
DateTime start = DateTime.Now;
bool PreCheck = chkPrecheck.Checked;
bool NoThrow = chkNoThrow.Checked;
int divisor = (chkZero.Checked ? 0 : 1);
int Iterations = Convert.ToInt32(txtIterations.Text);
int i = 0;
ExceptionTest x = new ExceptionTest();
int r = -2;
int stat = 0;
for(i=0; i < Iterations; i++)
{
try
{
r = x.TryDivide(divisor, PreCheck, NoThrow);
}
catch
{
stat = -3;
}
}
DateTime stop = DateTime.Now;
TimeSpan elapsed = stop - start;
txtTime.Text = elapsed.TotalMilliseconds.ToString();
txtReturn.Text = r.ToString();
txtStatus.Text = stat.ToString();
}
}
class ExceptionTest
{
public int TryDivide(int quotient, bool precheck, bool nothrow)
{
if (precheck)
{
if (quotient == 0)
{
if (nothrow)
{
return -9;
}
else
{
throw new DivideByZeroException();
}
}
}
else
{
try
{
int a;
a = 1 / quotient;
return a;
}
catch
{
if (nothrow)
{
return -9;
}
else
{
throw;
}
}
}
return -1;
}
}
所以是的,例外非常昂贵。
在有人说出来之前,是的,我在发布模式下进行了测试,而不仅仅是 Debug 模式。亲自尝试一下代码,看看是否会得到明显不同的结果。
答案 2 :(得分:7)
异常有两个成本:在异常基础结构中预热页面 - 如果没有进入内存然后进入CPU缓存 - 并且每次抛出成本来收集异常堆栈,搜索异常处理程序,可能调用异常过滤器,展开堆栈,调用终结块 - 运行时,设计,不优化的所有操作。
因此,衡量抛出异常的成本可能会产生误导。如果你编写一个迭代抛出并捕获异常的循环,抛出站点和catch站点之间没有大量工作,那么成本就不会那么大。但是,这是因为它正在摊还例外的预热成本,而且成本更难衡量。
当然,如果一个人的主要经验是调试器下的程序抛出异常,那么异常就不会花费任何费用。但它们确实有成本,特别是设计库是可取的,这样可以在必要时优化异常。
答案 3 :(得分:4)
您可以在此问题的答案中找到有关此问题的大量有用信息,包括45个上选票How slow are .net exceptions?
的一个答案答案 4 :(得分:3)
使用错误返回将比异常更昂贵 - 只要一段代码忘记检查错误返回,或者无法传播它。
但是,请务必不要使用控制流的异常 - 仅使用它们来表示出现问题。
答案 5 :(得分:2)
我在找到支持它的任何文档时遇到了麻烦,但请记住,当你抛出异常时,C#必须从你调用它的位置生成一个堆栈跟踪。堆栈痕迹(和一般的反射)远非自由。
答案 6 :(得分:1)
在最近的一些实际工作绩效分析中,我们发现低端计算机上的大量例外对应用程序性能有着至关重要的影响,以至于我们花费了几周的时间来完成并调整代码以避免抛出这么多。
当我说一个关键效果时,应用程序正在使用的单核CPU核心上将双核CPU高达95%。
答案 7 :(得分:1)
您应该更喜欢错误代码的异常,以及错误条件,但不要将异常用于正常的程序流。
与正常工作流程相比,异常是非常繁重的工作,我发现使用try-catch-block可以大大降低应用程序性能。
答案 8 :(得分:1)
抛出异常是一种相对便宜的操作。由于必须发现捕获处理程序,执行catch处理程序中的代码,查找finally块,执行finally块中的代码,然后返回到原始调用者,必须发生堆栈遍历才能捕获它们。
强烈建议您不要对控制流使用例外。使用返回代码来指示错误从“时间和材料”的角度来看是非常昂贵的,因为它最终会产生维护成本。
除此之外,你的两个例子不匹配,甚至没有效果。由于您返回b
,类型为Bar
,因此您的第一种方法应该是:
public Bar Foo(Bar b)
{
if(b.Success)
{
return b;
}
else
{
throw n.Exception;
}
}
可以改写为:
public Bar Foo(Bar b)
{
if (!b.Success)
throw n.Exception;
return b;
}
答案 9 :(得分:1)
一般来说,由于捕获异常的昂贵性质,我已经避免了这种情况。如果它不是经常发生的事情,那可能不会太糟糕。
但是,为什么不返回null?然后就变成了这个:
public Foo Bar(Bar b)
{
if(b.Success)
{
return b;
}
else
{
return null;
}
}
然后,无论何时调用Bar(),只需检查以确保在使用该值之前返回的值不为null。这是一个便宜得多的操作。我认为这是一个很好的做法,因为这是微软在许多.NET内置函数中使用的技术。
答案 10 :(得分:0)
我看到许多不同的系统,其中异常被视为软件问题的指示,并且还意味着提供有关导致它的事件的信息。这对系统来说总是很重要。然而,有些系统异常并不例外,因为语言本身使用相同或类似的方法从常规等返回。因此,它归结为异常如何嵌入语言和系统中。 我在java和我现在工作的专有系统中进行了测量,结果与预期相同 - 例外,例外(20-25)%更贵。
这不一定是规则。我想知道python如何站在这上面。我不再进行任何python开发了,所以我不打算进行调查。
作为一个可能有趣的轶事证据,我认为:在我在几年前工作的专有系统中,非协作使用协议违规的例外导致严重的问题和我们的一个生产系统的中断。例外用于表示从外部收到的消息中缺少或损坏的必填字段。一切都很好,直到一个阳光灿烂的日子,一些不太熟练的公司生产了一个节点,在投入运行时,造成了很多麻烦。必须删除例外,因为我们无法处理来自故障节点的低水平信令。故障确实也在我们这一边 - 而不是遵循协议规范描述如何处理格式错误的消息(忽略并执行信号故障计数器)我们试图检测和调试外部节点中的软件故障。 如果例外情况较便宜,那就不会那么大了。
事实上,在测试安全性的这些天里,我总是有一个TC或很少做到这一点 - 产生大量的协议违规情况。如果开发人员使用异常来处理那些,那么系统可以很容易地陷入困境。当这一次失败后,我将再次开始测量差异......