C#try-catch-else

从Python到C#的exception处理让我烦恼的一件事是,在C#中似乎没有任何指定else子句的方法。 例如,在Python中我可以写这样的东西(注意,这只是一个例子。我不是问什么是读取文件的最佳方法):

try { reader = new StreamReader(path); } catch (Exception) { // Uh oh something went wrong with opening the file for reading } else { string line = reader.ReadLine(); char character = line[30]; } 

从我在大多数C#代码中看到的,人们只会写下面的内容:

 try { reader = new StreamReader(path); string line = reader.ReadLine(); char character = line[30]; } catch (Exception) { // Uh oh something went wrong, but where? } 

这样做的问题在于我不希望因为文件中的第一行不能包含超过30个字符而超出范围exception。 我只想捕获与读取文件流有关的exception。 我可以在C#中使用任何类似的构造来实现相同的function吗?

捕获特定类别的exception

 try { reader = new StreamReader(path); string line = reader.ReadLine(); char character = line[30]; } catch (IOException ex) { // Uh oh something went wrong with I/O } catch (Exception ex) { // Uh oh something else went wrong throw; // unless you're very sure what you're doing here. } 

当然,第二个捕获是可选的。 而且由于你不知道发生了什么,吞下这个最普遍的例外是非常危险的。

你可以这样写:

 bool success = false; try { reader = new StreamReader(path); success = true; } catch(Exception) { // Uh oh something went wrong with opening the file for reading } finally { if(success) { string line = reader.ReadLine(); char character = line[30]; } } 

你可以这样做:

 try { reader = new StreamReader(path); } catch (Exception) { // Uh oh something went wrong with opening the file for reading } string line = reader.ReadLine(); char character = line[30]; 

但是,当然,您必须将reader设置为正确的状态或退出方法。

捕获更具体的例外情况。

 try { reader = new StreamReader(path); string line = reader.ReadLine(); char character = line[30]; } catch(FileNotFoundException e) { // thrown by StreamReader constructor } catch(DirectoryNotFoundException e) { // thrown by StreamReader constructor } catch(IOException e) { // some other fatal IO error occured } 

此外,通常,尽可能处理最具体的exception并避免处理基本System.Exception

.NET中的exception使用方式不同; 它们仅适用于特殊情况。

实际上,除非你知道它的含义,否则你不应该捕获exception,并且实际上可以对它做些什么。

您可以拥有多个catch子句,每个子句都特定于您希望捕获的exception类型。 因此,如果您只想捕获IOExceptions,那么您可以将catch子句更改为:

 try { reader = new StreamReader(path); string line = reader.ReadLine(); char character = line[30]; } catch (IOException) { } 

除了IOException之外的任何内容都会向上传播调用堆栈。 如果您还想处理其他exception,则可以添加多个exception子句,但必须确保将它们添加到大多数特定于最通用的顺序中。 例如:

 try { reader = new StreamReader(path); string line = reader.ReadLine(); char character = line[30]; } catch (IOException) { } catch (Exception) { } 

更惯用的是,您将使用using语句将文件打开操作与对其包含的数据所做的工作分开(并在退出时包括自动清理)

 try { using (reader = new StreamReader(path)) { DoSomethingWith(reader); } } catch(IOException ex) { // Log ex here } 

最好避免捕获每个可能的exception – 比如那些告诉你运行时即将到期的exception。

您也可以嵌套try语句

我可以在C#中使用任何类似的构造来实现相同的function吗?

没有。

使用“if”语句包装索引访问器,这是性能和可读性方面的最佳解决方案。

 if (line.length > 30) { char character = line [30]; } 

看到其他建议的解决方案后,这是我的方法:

 try { reader = new StreamReader(path); } catch(Exception ex) { // Uh oh something went wrong with opening the file stream MyOpeningFileStreamException newEx = new MyOpeningFileStreamException(); newEx.InnerException = ex; throw(newEx); } string line = reader.ReadLine(); char character = line[30]; 

当然,只有当您对通过打开文件流(此处作为示例)抛出的任何exception感兴趣时, 除了应用程序中的所有其他exception之外 ,这样做才有意义。 在应用程序的某个更高级别,您可以根据需要处理MyOpeningFileStreamException

由于未经检查的exception,您永远不能100%确定只从整个代码块中捕获IOException就足够了 – StreamReader也可以决定现在或将来抛出其他类型的exception。

我已经冒昧地改变你的代码,以展示一些重要的观点。

using构造用于打开文件。 如果抛出exception,即使您没有捕获exception,也必须记住关闭该文件。 这可以使用try { } catch () { } finally { }构造来完成,但using指令对此更好。 它保证当using块的范围结束时,将处理在内部创建的变量。 对于文件,这意味着它将被关闭。

通过研究StreamReader构造函数和ReadLine方法的文档,您可以看到可能会抛出哪些exception。 然后你可以抓住你认为合适的那些。 请注意,记录的exception列表并不总是完整的。

 // May throw FileNotFoundException, DirectoryNotFoundException, // IOException and more. try { using (StreamReader streamReader = new StreamReader(path)) { try { String line; // May throw IOException. while ((line = streamReader.ReadLine()) != null) { // May throw IndexOutOfRangeException. Char c = line[30]; Console.WriteLine(c); } } catch (IOException ex) { Console.WriteLine("Error reading file: " + ex.Message); } } } catch (FileNotFoundException ex) { Console.WriteLine("File does not exists: " + ex.Message); } catch (DirectoryNotFoundException ex) { Console.WriteLine("Invalid path: " + ex.Message); } catch (IOException ex) { Console.WriteLine("Error reading file: " + ex.Message); } 

听起来你只想在第一件事成功的时候做第二件事。 并且可能捕获不同类别的exception是不合适的,例如,如果两个语句都抛出同一类exception。

 try { reader1 = new StreamReader(path1); // if we got this far, path 1 succeded, so try path2 try { reader2 = new StreamReader(path2); } catch (OIException ex) { // Uh oh something went wrong with opening the file2 for reading // Nevertheless, have a look at file1. Its fine! } } catch (OIException ex) { // Uh oh something went wrong with opening the file1 for reading. // So I didn't even try to open file2 } 

在C#中try { ... } catch { ... } else { ... }可能没有任何原生支持,但如果您愿意承担使用变通方法的开销,那么下面显示的示例可能是吸引力:

 using System; public class Test { public static void Main() { Example("ksEE5A.exe"); } public static char Example(string path) { var reader = default(System.IO.StreamReader); var line = default(string); var character = default(char); TryElse( delegate { Console.WriteLine("Trying to open StreamReader ..."); reader = new System.IO.StreamReader(path); }, delegate { Console.WriteLine("Success!"); line = reader.ReadLine(); character = line[30]; }, null, new Case(typeof(NullReferenceException), error => { Console.WriteLine("Something was null and should not have been."); Console.WriteLine("The line variable could not cause this error."); }), new Case(typeof(System.IO.FileNotFoundException), error => { Console.WriteLine("File could not be found:"); Console.WriteLine(path); }), new Case(typeof(Exception), error => { Console.WriteLine("There was an error:"); Console.WriteLine(error); })); return character; } public static void TryElse(Action pyTry, Action pyElse, Action pyFinally, params Case[] pyExcept) { if (pyElse != null && pyExcept.Length < 1) { throw new ArgumentException(@"there must be exception handlers if else is specified", nameof(pyExcept)); } var doElse = false; var savedError = default(Exception); try { try { pyTry(); doElse = true; } catch (Exception error) { savedError = error; foreach (var handler in pyExcept) { if (handler.IsMatch(error)) { handler.Process(error); savedError = null; break; } } } if (doElse) { pyElse(); } } catch (Exception error) { savedError = error; } pyFinally?.Invoke(); if (savedError != null) { throw savedError; } } } public class Case { private Type ExceptionType { get; } public Action Process { get; } private Func When { get; } public Case(Type exceptionType, Action handler, Func when = null) { if (!typeof(Exception).IsAssignableFrom(exceptionType)) { throw new ArgumentException(@"exceptionType must be a type of exception", nameof(exceptionType)); } this.ExceptionType = exceptionType; this.Process = handler; this.When = when; } public bool IsMatch(Exception error) { return this.ExceptionType.IsInstanceOfType(error) && (this.When?.Invoke(error) ?? true); } }