C# 试图理解C语言中的异常#

C# 试图理解C语言中的异常#,c#,.net,exception,exception-handling,C#,.net,Exception,Exception Handling,我从来没有在我的代码中使用过任何try/catch,但我正试图打破这个习惯,现在开始使用异常 我认为在我的应用程序中最重要的地方是读取一个文件,我现在正在尝试实现它,但我不确定这样做的“最佳实践”。目前我正在做这样的事情: private void Parse(XDocument xReader) { IEnumerable<XElement> person = xReader.Descendants("Person").Elements(); foreach (X

我从来没有在我的代码中使用过任何try/catch,但我正试图打破这个习惯,现在开始使用异常

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

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.");
}
但是,当显示e.Message时,它只显示通用的KeyNotFoundException错误消息,而不是自定义错误消息。另外,我也不确定总体上我是否正确地处理了整个“异常处理”。我确实返回了catch,因为如果文件没有成功读取,显然我只是想假装用户从未尝试打开一个文件,然后让他用另一个文件再试一次

我这样做对吗?同样,我对使用异常是相当陌生的,我想确保在继续并将其应用到我的程序的其余部分之前,我已经记下了异常


还有,为什么人们说不要做捕获(例外e)?在本例中,我似乎希望这样做,因为无论在读取文件时发生什么错误,如果出现错误,我都希望停止读取文件,显示错误消息,然后返回。难道不是一直都是这样吗?我可以理解,如果您希望根据异常以不同的方式处理某个异常,那么不希望处理异常e,但在这种情况下,我不想只处理基本异常类,以防出现任何错误吗?

我不太清楚您为什么没有收到自定义错误消息。这应该会发生(除非是其他东西抛出了
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();
}
不捕获任何旧的
异常e
是一种好的做法,因为您只想捕获并处理期望得到的异常。如果你得到了一种你不希望得到的不同类型的异常,通常这意味着一些新奇的东西以你不希望的方式失败了,你希望这种行为是明显的,而不仅仅是被所有常规的错误处理代码掩盖起来

许多生产级系统在整个程序中都会有一个大的try/catch,可以捕获任何异常并在正常崩溃之前执行日志记录和清理。这可以通过在代码中更深层地使用特定的try/catch块来补充,这些块以定义良好的方式处理预期的异常。对于意外的异常,您总是可以让CLR不愉快地爆炸,并从中找出发生了什么

下面是一个新的例外的例子。如果出现严重错误,在这一行:

IEnumerable<XElement> person = xReader.Descendants("Person").Elements();
IEnumerable person=xReader.substands(“person”).Elements();
…您得到了一个
OutOfMemoryException
?你真的应该向用户显示一个弹出窗口,让你的程序像正常一样运行,即使它根本不可能正常运行?如果由于您在
OutOfMemoryException
上以静默方式失败,您随后尝试取消对空引用的引用,并获得导致程序崩溃的
NullReferenceException
,又会怎样呢?要想找出引用为空的根本原因,您将花费大量时间


解决bug的最佳方法是快速失败并发出噪音。

就您正在做的事情而言,它看起来基本正常。我不能说你是否应该在那个特定的点抛出异常,但是抛出和捕获是正确的。就信息而言,它应该按照目前的方式运作。尝试显示
e.ToString()
以查看调用堆栈。可能是简单地执行
person[“Name”]
就是首先抛出
KeyNotFoundException

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

例如,如果您获得
KeyNotFoundException
,您可能会提到文件格式不正确的原因,并且可能会在屏幕上向用户显示文件。如果您得到
FileNotfoundException
,您可以向他们显示路径,并打开
OpenFileDialog
让他们选择一个新文件。由于安全权限导致的异常您可以显示说明,让它们提升您的权限。有些异常甚至可能是可恢复的(可能一个元素的格式不好,但其余的都可以;如果整个元素都失败了呢?)


但是,如果你想这样设计的话,抓住一切都是可以的。最可靠的程序将捕获所有可能的异常并以非常具体的方式处理,而不是向用户呈现原始异常。它有助于获得更好的用户体验,并为您提供解决可能出现的问题的方法。

大多数情况下,您可能不关心您遇到的异常类型,因此捕获一般的
异常是可以的,但是在某些特定情况下,您实际上希望捕获相关异常(不仅仅是一般的
异常

一个特别的例子是,如果您有一个线程,并且希望通过阻塞调用中断它,那么在这种情况下,您必须区分
中断异常
异常

考虑这个例子:你有一个线程
IEnumerable<XElement> person = xReader.Descendants("Person").Elements();
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();
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.");
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;
    }
}
throw new MyGoodExceptionType ("Could not read file", e);  // e is caught inner root cause.
if (!person.ContainsKey("Name"] ||
    !person.ContainsKey("Job"] ||
    !person.ContainsKey("HairColor"))