繁体   English   中英

试图理解C#中的异常

[英]Trying to understand exceptions in C#

我在代码中并没有真正使用任何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) 看起来在这种情况下我想要这样做,因为无论在读取文件时发生什么错误,如果有错误,我想停止读取文件,显示错误消息,然后返回。 这不总是这样吗? 我可以理解不想处理异常e如果你想根据异常处理不同的东西但是在这种情况下我不想只是处理基本异常类以防出现任何问题?

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

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

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

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

一些典型的错误:

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

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

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

希望有所帮助。

好的,先......

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

文件明确指出:

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

与之相比:

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

现在:

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

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

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

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

例如,如果您收到KeyNotFoundException ,您可能会提到有关文件格式不正确的信息,并可能在屏幕上向用户显示该文件。 如果你得到FileNotfoundException ,你可以向他们展示路径并打开一个OpenFileDialog让他们选择一个新文件。 由于安全权限而导致的例外情况,您可以显示说明以提升您的权限。 有些异常甚至可能是可恢复的(也许一个元素格式错误,但其余的都没问题;它是否会使整个事情失败?)

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

大多数情况下,您可能不关心您获得的异常类型,因此捕获泛型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 ,这需要一个键查找,如果键不在地图中,那么你可能会得到一个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 :如果person["Name"] == null计算结果为true,则键“Name”为实际上在person字典中找到,因此抛出KeyNotFoundException会误导任何捕获该异常的人。 如果你的值为null ,那么抛出异常可能是不可取的......这确实不是一个例外情况。 您可以返回一个标志,指示未找到密钥:

public bool PerformRead(/*... parameters ...*/)
{
    foreach (XElement e in person)
    {
        // Avoid getting the KeyNotFoundException
        if(!person.ContainsKey(e.Name.ToString()))
        {
            person.Add(e.Name.ToString(), "some default value");
        }
        person[e.Name.ToString()] = e.Value;
    }

    if (person["Name"] == null || person["Job"] == null || person["HairColor"] == null)
    {
        return false;
    }
    else
    {
        return true;
    }
}

我不太清楚为什么你没有收到自定义错误消息。 应该发生这种情况(除非是其他东西抛出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 ,该怎么办? 你将有一个时间的魔鬼试图找出为什么该引用为空的根本原因。

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

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

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

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

person["Name"] == null || person["Job"] == null || person["HairColor"] == null person["Name"] == null || person["Job"] == null || person["HairColor"] == null 如果任何这些键不在您的词典中, person["Name"] == null || person["Job"] == null || person["HairColor"] == null将失败。

你需要这样做:

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM