C# 例如「;“使用异常控制流”;

C# 例如「;“使用异常控制流”;,c#,exception,exception-handling,C#,Exception,Exception Handling,“使用异常控制流”的一段代码是什么样子的?我试图找到一个直接的C#示例,但找不到。为什么不好 非常感谢 下面的代码捕获了一个可以轻松避免的异常。这使得代码更难遵循,并且通常会导致性能成本 int input1 = GetInput1(); int input2 = GetInput2(); try { int result = input1 / input2; Output("{0} / {1} = {2}", input1, input2, result); } catch

“使用异常控制流”的一段代码是什么样子的?我试图找到一个直接的C#示例,但找不到。为什么不好

非常感谢 下面的代码捕获了一个可以轻松避免的异常。这使得代码更难遵循,并且通常会导致性能成本

int input1 = GetInput1();
int input2 = GetInput2();

try
{
    int result = input1 / input2;
    Output("{0} / {1} = {2}", input1, input2, result);
}
catch (OverflowException)
{
    Output("There was an overflow exception. Make sure input2 is not zero.");
}
更好 此代码检查是否存在引发异常的条件,并在错误发生之前更正该情况。这样就没有例外了。代码更具可读性,而且性能很可能会更好

int input1 = GetInput1();
int input2 = GetInput2();

while (input2 == 0)
{
    Output("input2 must not be zero. Enter a new value.");
    input2 = GetInput2();
}

int result = input1 / input2;
Output("{0} / {1} = {2}", input1, input2, result);

根据定义,异常是发生在软件正常流程之外的事件。一个简单的例子是使用
FileNotFoundException
查看文件是否存在

try
{
    File.Open(@"c:\some nonexistent file.not here");
}
catch(FileNotFoundException)
{
    // do whatever logic is needed to create the file.
    ...
}
// proceed with the rest of your program.
在本例中,您没有使用
File.Exists()
方法,该方法实现了相同的结果,但没有异常的开销

除了使用不当之外,还有与异常、填充属性、创建堆栈跟踪等相关的开销。

这里有一个常见的开销:

public bool TryParseEnum<T>(string value, out T result)
{
    result = default(T);

    try
    {
        result = (T)Enum.Parse(typeof(T), value, true);
        return true;
    }
    catch
    {
        return false;
    }
}
public bool TryParseEnum(字符串值,out T结果)
{
结果=默认值(T);
尝试
{
result=(T)Enum.Parse(typeof(T),value,true);
返回true;
}
抓住
{
返回false;
}
}

一个例子是使用异常从递归方法返回结果:

public void Search(Node node, object data)
{
    if(node.Data.Equals(data))
    {
        throw new ResultException(node);
    }
    else
    {
        Search(node.LeftChild, data);
        Search(node.RightChild, data);
    }    
}
这样做是一个问题,原因有几个

  • 这完全违反直觉。例外情况是为例外情况设计的。按照预期工作的东西(我们希望)永远不会是例外情况
  • 您不能总是依赖于抛出并传播给您的异常。例如,如果异常抛出代码在单独的线程中运行,则需要一些额外的代码来捕获它
  • 这是一个潜在的性能问题。异常会带来开销,如果抛出很多异常,应用程序的性能可能会下降
  • 关于这个问题,还有几个例子和一些有趣的讨论


    免责声明:上面的代码是根据wiki页面上的第一个示例改编的,将其转换为C#。

    它大致相当于一个goto,除了在单词Exception方面更糟糕,而且开销更大。您告诉代码跳转到catch块:

    bool worked;
    try
    {
        foreach (Item someItem in SomeItems)
        {
            if (someItem.SomeTestFailed()) throw new TestFailedException();
        }
        worked = true;
    }
    catch(TestFailedException testFailedEx)
    {
        worked = false;
    }
    if (worked) // ... logic continues
    
    正如您所看到的,它正在运行一些(虚构的)测试;如果失败,将引发异常,并且
    已工作
    将设置为
    false

    当然,直接更新
    bool工作起来要容易得多

    希望有帮助

    我不喜欢C#但是你可以看到try-catch-finally语句和普通控制流语句之间的一些相似之处

    想想看,每当您
    抛出
    异常时,您就会强制将控件传递给
    catch
    子句。所以如果你有

    if (doSomething() == BAD) 
    {
      //recover or whatever
    }
    
    您可以很容易地用try-catch来理解它:

    try
    {
      doSomething();
    }
    catch (Exception e)
    {
      //recover or do whatever
    }
    
    异常的强大之处在于,您不必在同一个主体中改变程序流,您可以随时抛出异常,同时保证控制流会突然发散并到达catch子句。这很强大,但同时也很危险,因为您可能会在最后执行一些需要备份的操作,这就是
    finally
    语句存在的原因

    此外,您还可以对
    while
    语句建模,而无需有效地使用它的条件:

    while (!finished)
    {
      //do whatever
    }
    
    可以成为

    try
    {
      while (true)
      {
         doSomethingThatEventuallyWillThrowAnException();
      }
    }
    catch (Exception e)
    {
      //loop finished
    }
    

    可能是我见过的最严重的违规行为:

    // I haz an array...
    public int ArrayCount(object[] array)
    {
        int count = 0;
        try
        {
            while (true)
            {
                var temp = array[count];
                count++;
            }
        }
        catch (IndexOutOfRangeException)
        {
            return count;
        }
    }
    

    我目前正在与一个第三方程序合作,该程序可以做到这一点。它们有一个“游标”接口(基本上是一个IEnumerable替代),在这个接口中,告诉程序您已经完成的唯一方法就是引发一个异常。代码基本上如下所示:

    // Just showing the relevant section
    bool finished = false;
    
    public bool IsFinished()
    {
        return finished;
    }
    
    // Using something like:
    // int index = 0;
    // int count = 42;
    
    public void NextRecord()
    {
        if (finished)
            return;
    
        if (index >= count)
            throw new APIProgramSpecificException("End of cursor", WEIRD_CONSTANT);
        else
            ++index;
    }
    
    // Other methods to retrieve the current value
    

    不用说,我讨厌API,但它是流量控制异常的一个很好的例子(也是一种疯狂的工作方式)。

    合作伙伴开发的模块导致我们的应用程序需要很长时间才能加载。仔细检查后,模块在应用程序启动时查找配置文件。这一点本身并不令人反感,但它的做法却非常糟糕:

    对于应用程序目录中的每个文件,它都会打开该文件并尝试将其解析为XML。如果一个文件抛出了一个异常(因为它不是XML),它会捕获该异常,压制它,然后尝试下一个文件

    合作伙伴测试此模块时,应用程序目录中只有3个文件。bonehead配置文件搜索对测试应用程序启动没有明显影响。当我们将其添加到应用程序中时,应用程序目录中有100个文件,应用程序在启动时冻结了近一分钟

    为了向伤口中添加盐,模块搜索的配置文件的名称是预先确定的且恒定的。不需要任何形式的文件搜索


    天才是有限度的。愚蠢是无限的。

    这是一个很好的例子,特别是当一个好的替代方案--
    TryParse
    --已经存在时+1-我在我的代码中看到过这一点-它在排序过程中多次被调用,并导致性能问题。使用ChrisF所说的TryParse解决了性能问题。然而,我相信这是.NET2.0之前的唯一方法。哦,伙计,你真的看到了吗?实际上我想我是从《每日WTF》上偷来的。所以我只是在一个网站上“看到”了。信不信由你,在VBA中,查询维度数或数组的唯一方法是一直尝试直到出现错误。+1而其他一些示例也在控制流,我认为这是一个被警告的噩梦场景。我一直认为流控制异常通常都是自定义异常,而不是像FileNotF这样的内置异常