试图理解C#中的异常

时间:2011-08-30 03:35:34

标签: c# .net exception exception-handling

我的代码中并没有真正使用任何try / catches,但我试图打破这种习惯,现在开始使用异常。

我认为在我的应用程序中拥有它的最重要的地方是读取文件,我现在正在尝试实现它,但我不确定这样做的“最佳实践”。目前我正在做这样的事情:

private void Parse(XDocument xReader)
{
    IEnumerable<XElement> person = xReader.Descendants("Person").Elements();

    foreach (XElement e in person)
        personDic[e.Name.ToString()] = e.Value;

    if (personDic["Name"] == null || personDic["Job"] == null || personDic["HairColor"] == null)
        throw new KeyNotFoundException("Person element not found.");
}

但我不确定这是否正确。我有这个来处理它:

try
{
    personsReader.Read(filename, persons);
}
catch (KeyNotFoundException e)
{
    MessageBox.Show(e.Message);

    return;
}

// Do stuff after reading in the file..

但是,在显示e.Message时,它只显示通用的KeyNotFoundException错误消息,而不是自定义错误消息。此外,我不确定一般情况下,我是否正确地处理这整个“异常处理的东西”。我确实在catch中返回,因为如果文件未成功读取,我只是想假装用户从未尝试打开文件并让他再次尝试使用其他文件。

我这样做得对吗?我再次使用异常相当新,我想确保在继续将其应用到我的程序的其余部分之前将其删除。

另外,为什么人们说不做catch (Exception e)?看起来在这种情况下我想要这样做,因为无论在读取文件时发生什么错误,如果有错误,我想停止读取文件,显示错误消息,然后返回。这不总是这样吗?我可以理解不想处理Exception e如果你想根据异常处理不同的东西但是在这种情况下我不想只是处理基本异常类以防出现任何问题吗?

7 个答案:

答案 0 :(得分:5)

当你处理条件并做一些有用的事情时,你应该捕获异常。否则你应该让它冒泡调用堆栈,也许你上面的人可以处理它。有些应用程序有未处理的异常处理程序来处理最外层,但一般情况下,除非你知道有一些有用的方法来处理它,否则就让它去吧。

在您的情况下,您处理的是无法读取资源并通知用户。你正在处理它。关于泛型异常,你可以做的一件事就是捕获并重新抛出一个更好的异常。如果这样做,请确保将根本原因异常作为内部异常。如果合适,您还可以跟踪或记录详细信息。

throw new MyGoodExceptionType ("Could not read file", e);  // e is caught inner root cause.

现在UI显示了一个很好的错误,也许内部根本原因在于日志等...

一些典型的错误:

  • 在通用库方法中处理堆栈深处的异常:请记住,可以在许多不同的代码路径中调用公共库函数。您可能没有上下文是否应该处理以及是否适合处理它。堆栈中较高的调用者可能具有上下文并知道它是否可以安全处理。通常,这意味着更高层的代码决定处理。在较低层,通常让它们流动。

  • 吞咽异常:有些代码会捕获异常(特别是堆栈中较低的异常),然后根条件就会消失,这使得调试变得令人抓狂。一旦痛风,如果你能处理它,那就这样做。如果没有,就放手吧。

  • 异常应该例外:不要使用激励进行流量控制。例如,如果您正在读取资源,请不要尝试阅读然后捕获异常并制定决策点。相反,调用ifexists,检查bool并在代码中做出决定。当您将调试器设置为中断异常时,这尤其有用。你应该能够运行干净,如果调试器中断,它应该是一个真正的问题。调试时调试器不断中断是有问题的。我个人非常喜欢极少抛出异常,并且总是试图避免流量控制。

希望有所帮助。

答案 1 :(得分:3)

好的,首先......

...这不是KeynotFoundException,它应该是ArgumentException ....提供的Argument无效。

文件明确指出:

  

指定用于访问集合中元素的键时引发的异常   不匹配集合中的任何键。

将其与:

进行比较
  

提供给方法的其中一个参数无效时抛出的异常

现在:

  

另外,为什么人们说不捕捉(例外e)?

因此,这会吞下异常并使其无法进行集中错误处理/日志记录。只处理你期望的异常,除非它是一个catch / close某事或者记录/重新抛出(即throw;)。然后有一个中央appdomain处理程序,它获取每个未捕获的除外并记录它;)它无法处理任何事情 - 因为该级别的异常是意外的。它应该基本上将摘要写入文件并完成,可能使用UI(应用程序已重新启动)。

答案 2 :(得分:1)

就你所做的而言,它看起来很好。我不能说你是否应该在特定点抛出异常,但抛出和捕获是正确完成的。至于消息,它应该按照它的方式工作。尝试显示e.ToString()以查看调用堆栈。可能只是简单地执行person["Name"]首先抛出KeyNotFoundException

关于捕捉Exception的问题,并不总是坏事。有时你无法预测所有可能的异常,有时候处理任何可能的失败都是件好事。但是,它无法以不同方式处理特定异常。

例如,如果您获得KeyNotFoundException,则可能会提及有关文件格式错误的方法,并可能在屏幕上向用户显示该文件。如果您获得FileNotfoundException,则可以向他们显示路径并打开OpenFileDialog以让他们选择新文件。由于安全权限而导致的例外情况,您可以显示说明以提升您的权限。有些例外甚至可以恢复(可能有一个元素格式错误,但其余的都没问题;如果整个事情都失败了吗?)

但是,如果您想要设计它,可以抓住所有内容。最稳定的程序将捕获每个可能的异常并以非常具体的方式处理它,而不是向用户呈现原始异常。它可以提供更好的用户体验,并为您提供解决可能发生的问题的方法。

答案 3 :(得分:1)

大多数情况下,您可能不关心您获得的异常类型,因此捕获泛型Exception很好,但是在某些特定情况下您实际上想要捕获相关异常(而不仅仅是通用Exception)。

一个特别的例子是,如果你有一个线程并且想要从阻塞调用中断它,那么你必须区分InterruptExceptionException

考虑这个例子:你有一个线程,每分钟运行Read 5分钟(这不是一个非常现实的例子,但它应该让你知道为什么要处理不同的例外)。您必须在5分钟后停止该线程,因为您的应用程序将要关闭,并且您不想再等待一分钟才能读取running标志...毕竟,您不希望您的用户等待整整一分钟才关闭应用程序。为了立即停止线程,将标志设置为false,并在线程上调用Interrupt。在这种情况下,您必须特别捕获ThreadInterrupted异常,因为它告诉您应该退出循环。如果你发现另一个异常,那么你就无法执行任务,但是你不想一直放弃这份工作,你想在下一分钟再试一次。这描述了您的要求如何规定您需要处理的异常类型。以下是代码中的示例:

bool running = true;

Thread t = new Thread(()=>
{
    while(running)
    {
        try
        {
            // Block for 1 minute
            Thread.Sleep(60*1000); 

            // Perform one read per minute
            personsReader.Read(filename, persons);
        }
        catch (KeyNotFoundException e)
        {
            // Perform a specific exception handling when the key is not found
            // but do not exit the thread since this is not a fatal exception
            MessageBox.Show(e.Message);
        }
        catch(InterruptException)
        {
            // Eat the interrupt exception and exit the thread, because the user
            // has signalled that the thread should be interrupted.
            return;
        }
        catch(Exception e)
        {
            // Perform a genetic exception handling when another exception occurs
            // but do not exit the thread since this is not a fatal error.
            MessageBox.Show("A generic message exception: " + e.Message);
        }
    }
});

t.IsBackground = true;
t.Start();

// Let the thread run for 5 minutes
Thread.Sleep(60*5000);

running = false;
// Interrupt the thread
t.Interrupt();

// Wait for the thread to exit
t.Join();

现在解决您的其他问题,但未显示异常:请注意您正在访问需要密钥查找的person[e.Name.ToString()] = e.Value,如果密钥不在地图中,那么您可能会获得{{1 }}。这将是您正在捕获的一般异常,并且永远不会抛出您的自定义异常,因为KeyNotFoundException可能会在您访问代码之前抛出。

person[e.Name.ToString()]

此外,当您实际找到密钥但未找到相应的值时,您不想抛出foreach (XElement e in person) person[e.Name.ToString()] = e.Value; // <-- May be throwing the KeyNotFoundException if (person["Name"] == null || person["Job"] == null || person["HairColor"] == null) throw new KeyNotFoundException("Person element not found."); :如果KeyNotFoundException的计算结果为true,那么密钥“名称“实际上是在person["Name"] == null词典中找到的,因此抛出person会误导任何捕获该异常的人。在您的值为KeyNotFoundException的情况下,无论如何都不建议抛出异常......这确实不是特例。您可以返回一个标志,指示未找到密钥:

null

答案 4 :(得分:0)

我不太确定您为什么没有收到自定义错误消息。这应该发生(除非是 else 投掷KeyNotFoundException,而不是你明确投掷的那个)。

此外,通常您应该将所有依赖于文件读取的代码放在try内,这通常是您方法的其余部分。您不再需要在catch块内返回,并且依赖于文件读取成功的后续代码仍可在失败后执行。

示例:

public static void Main()
{
    var filename = "whatever";

    try
    {
        personsReader.Read(filename, persons);
        var result = personsReader.DoSomethingAfterReading();
        result.DoSomethingElse();
    }
    catch (KeyNotFoundException e)
    {
        MessageBox.Show(e.Message);
    }
    finally
    {
        personsReader.CloseIfYouNeedTo();
    }

    DoSomeUnrelatedCodeHere();
}

最好不要捕捉任何旧Exception e的原因是因为你只想抓住并处理你期望获得的异常。如果你得到了一个你不希望得到的另类异常,通常这意味着一些小说以你没想到的方式失败了,你希望这种行为是显而易见的,而不仅仅是在地毯下扫过所有常规的错误处理代码。

许多生产级系统将围绕整个程序进行一次大的尝试/捕获,捕获任何异常,并在正常崩溃之前执行日志记录和清理。这可以通过在代码内部更深入地使用特定的try / catch块来实现,这些块以明确定义的方式处理预期的异常。对于意外的例外情况,你总是可以让CLR无意识地炸弹并弄清楚发生了什么。

这是一个新颖例外的例子。如果出现严重错误并且在这一行中该怎么办:

IEnumerable<XElement> person = xReader.Descendants("Person").Elements();

...你得到OutOfMemoryException?你是否真的只是向用户显示一个弹出窗口并允许你的程序试着像往常一样继续,即使它根本没有办法可以吗?如果因为你在OutOfMemoryException上无声地失败了,你以后会尝试取消引用空引用,并获得导致程序崩溃的NullReferenceException,该怎么办?您将有一段时间试图找出该引用为空的根本原因。

排除错误的最佳方法是快速失败并吵闹失败。

答案 5 :(得分:0)

“例外情况属于特殊情况” - 未知

不要使用to本质地将方法中的消息传递给调用方法。总是试着优雅地处理事情。当一些奇怪的事情发生时,抛出异常。这是一个不熟悉如何使用异常的东西。

答案 6 :(得分:0)

在您的代码中,当您评估xxx语句中的条件时,您将触发if

如果您的词典中没有任何这些键,则询问person["Name"] == null || person["Job"] == null || person["HairColor"] == null将失败。

你需要这样做:

if (!person.ContainsKey("Name"] ||
    !person.ContainsKey("Job"] ||
    !person.ContainsKey("HairColor"))

因此,您抛出异常的调用永远不会被执行! 这就是为什么你永远不会看到你的信息。

我会养成不为这种编码做例外的习惯。例外是昂贵的,并且可能导致隐藏代码中的实际问题。

不要捕获一般例外,也不要为非特殊情况创建例外。