试图理解C#中的exception
我在代码中并没有真正使用任何try / catches,但我试图打破这种习惯,现在开始使用exception。
我认为在我的应用程序中最重要的地方是阅读文件而我现在正在尝试实现它,但我不确定这样做的“最佳实践”。 目前我正在做这样的事情:
private void Parse(XDocument xReader) { IEnumerable 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错误消息,而不是自定义错误消息。 此外,我不确定一般情况下我是否正确地处理了这整个“exception处理的东西”。 我确实在catch中返回,因为如果文件未成功读取,我只是想假装用户从未尝试打开文件并让他再次尝试使用其他文件。
我这样做了吗? 我再次使用exception相当新,我想确保在继续将其应用到我的程序的其余部分之前将其解决。
另外,为什么人们说不catch (Exception e)
? 看起来在这种情况下我想要这样做,因为无论在读取文件时发生什么错误,如果有错误,我想停止读取文件,显示错误消息,然后返回。 这不总是这样吗? 我可以理解不想处理exceptione,如果你想根据exception处理不同的东西,但在这种情况下,我不想只是处理基本exception类,以防出现任何问题?
当你能够处理这个条件并做一些有用的事情时,你应该捕获exception。 否则你应该让它冒泡调用堆栈,也许你上面的人可以处理它。 有些应用程序有未处理的exception处理程序来处理最外层,但一般来说,除非你知道你有一些有用的方法来处理它,否则就去吧。
在您的情况下,您处理的是无法读取资源并通知用户。 你正在处理它。 关于一般exception,你可以做的一件事是捕获并重新抛出一个更好的exception。 如果这样做,请确保将根本原因exception作为内部exception。 您还可以根据需要跟踪或记录详细信息。
throw new MyGoodExceptionType ("Could not read file", e); // e is caught inner root cause.
现在UI显示了一个很好的错误,也许内部根本原因是在日志等…
一些典型的错误:
-
在通用库方法中处理堆栈深处的exception:请记住,可以在许多不同的代码路径中调用公共库函数。 您可能没有上下文是否应该处理以及是否适合处理它。 堆栈中较高的调用者可能具有上下文并知道它是否可以安全处理。 通常,这意味着更高层的代码决定处理。 在较低层,通常让它们流动。
-
吞咽exception:某些代码捕获exception(特别是堆栈中较低的exception),然后根条件就会消失,从而使调试变得令人抓狂。 一旦痛苦,如果你能处理它,那就这样做。 如果没有,就放手吧。
-
例外应该是例外:不要使用excpetions进行流量控制。 例如,如果您正在阅读资源,请不要尝试阅读然后捕获exception并制定决策点。 相反,调用ifexists,检查bool并在代码中做出决定。 当您将调试器设置为中断exception时,这尤其有用。 您应该能够运行干净,如果调试器中断,它应该是一个真正的问题。 在调试时调试器不断中断是有问题的。 我个人非常喜欢极少抛出exception,并总是试图避免流量控制。
希望有所帮助。
好的,先……
…这不是KeynotFoundException,它应该是ArgumentException ….提供的Argument无效。
文件明确指出:
指定用于访问集合中的元素的键与集合中的任何键不匹配时引发的exception。
与之相比:
提供给方法的其中一个参数无效时引发的exception
现在:
另外,为什么人们说不捕捉(例外e)?
因此,吞下exception并使得无法进行集中error handling/记录。 只处理你期望的exception,除非它是一个catch / close某事或者记录/重新抛出(即throw;)。 然后有一个中央appdomain处理程序,它获取每个未捕获的除外并记录它;)它无法处理任何事情 – 因为该级别的exception是意外的。 它应该基本上将摘要写入文件并完成,可能使用UI(应用程序具有重新启动)。
就你正在做的事情来说,它看起来很好。 我不能说你是否应该在那个特定的点上抛出exception,但抛出和捕捉是正确的。 至于消息,它应该按照它的方式工作。 尝试显示e.ToString()
以查看调用堆栈。 可能只是简单地做人person["Name"]
首先抛出KeyNotFoundException
。
至于捕获Exception
的问题,它并不总是坏的。 有时你无法预测所有可能的exception,有时候处理任何可能的失败都是件好事。 但是,它无法以不同方式处理特定exception。
例如,如果您收到KeyNotFoundException
,您可能会提到有关文件格式不正确的信息,并可能在屏幕上向用户显示该文件。 如果你得到FileNotfoundException
,你可以向他们展示路径并打开一个OpenFileDialog
让他们选择一个新文件。 由于安全权限而导致的例外情况,您可以显示说明以提升您的权限。 有些例外甚至可以恢复(也许一个元素格式错误,但其余的都没问题;它是否会使整个事情失败?)
但是,如果您想要设计它,那么可以捕获所有内容。 最可靠的程序将捕获每个可能的exception并以非常具体的方式处理它,而不是向用户呈现原始exception。 它可以提供更好的用户体验,并为您提供解决可能发生的问题的方法。
大多数情况下,您可能不关心您获得的exception类型,因此捕获genericsException
很好,但是在某些特定情况下您实际上希望捕获相关exception(而不仅仅是通用Exception
)。
一个特别的例子是如果你有一个线程并且你想要从阻塞调用中断它,那么你必须区分InterruptException
和Exception
。
考虑这个例子:你有一个线程,每分钟运行Read
5分钟(这不是一个非常现实的例子,但它应该让你知道你为什么要处理不同的例外)。 您必须在5分钟后停止线程,因为您的应用程序将关闭并且您不想再等待一分钟才能读取running
标志…毕竟,您不希望您的用户等待只需关闭应用程序一整分钟。 为了立即停止线程,将标志设置为false,并在线程上调用Interrupt
。 在这种情况下,您必须捕获ThreadInterrupted
exception,因为它告诉您应该退出循环。 如果你发现另一个例外,那么你就没能完成任务,但是你不想一直放弃这份工作,你想在下一分钟再试一次。 这描述了您的要求如何规定您需要处理的exception类型。 以下是代码中的示例:
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();
现在你的另一个问题是exception没有显示:请注意你正在访问person[e.Name.ToString()] = e.Value
,这需要一个键查找,如果键不在地图中,那么你可能会得到一个KeyNotFoundException
。 这将是您正在捕获的一般exception,并且永远不会抛出您的自定义exception,因为在您访问代码之前, 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
会误导任何捕获该exception的人。 如果你的值为null
,那么抛出exception可能是不可取的......这确实不是一个例外情况。 您可以返回一个标志,指示未找到密钥:
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
的原因是因为你只想捕获和处理你期望获得的exception。 如果你得到一种你不希望得到的不同类型的exception,通常这意味着一些小说以你没想到的方式失败了,并且你希望这种行为是显而易见的,而不仅仅是在地毯下扫过所有常规error handling代码。
许多生产级系统将在整个程序中进行一次大的尝试/捕获,捕获任何exception并执行日志记录和清理,然后才能正常崩溃。 这可以通过在代码中更深入地使用特定的try / catch块来实现,这些块以明确定义的方式处理预期的exception。 对于意外的例外情况,您可以随时让CLR无意识地炸弹并弄清楚发生了什么。
这是一个新颖例外的例子。 如果出现严重错误并且在这一行中会怎么样:
IEnumerable person = xReader.Descendants("Person").Elements();
…你得到一个OutOfMemoryException
? 你是否真的只是向用户显示一个弹出窗口并允许你的程序试着像往常那样继续进行,即使它根本无法进行? 如果因为你在OutOfMemoryException
上无声地失败,你稍后会尝试取消引用空引用,并获得导致程序崩溃的NullReferenceException
,该怎么办? 您将有一段时间试图找出该引用为空的根本原因。
克服错误的最佳方法是快速失败并吵闹失败。
“例外情况属于特殊情况” – 未知
不要使用to本质上将方法中的消息传递给调用方法。 总是试着优雅地处理事情。 当一些奇怪的事情发生时,抛出exception。 这是一个不熟悉如何使用exception的东西。
在您的代码中,当您评估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"))
因此,您抛出exception的调用永远不会被执行! 这就是为什么你永远不会看到你的信息。
我会保持不为这种编码做例外的习惯。 例外是昂贵的,可能会导致隐藏代码中的实际问题。
不要捕获一般exception,也不要为非特殊情况创建exception。