C#学习笔记(三)—–C#高级特性:try语句和异常

try语句和异常

  • try语句是为了处理错误或者清理对象而定义的语句块。try语句后面必须定义catch语句块或finnally语句块或者后两者都有,当try块执行发生错误时,执行catch块,当结束try块或catch块时,有finally块的情况下会立即进入finally块来清理对象。
  • catch块可以访问到异常(Exception),该对象包含错误,catch块可以弥补错误也可以再次抛出异常,如果你想记录错误,你可以再次抛出异常,或者你想抛出一个新的、更高级别的异常。
  • finally块在代码中有决定性的作用,因为他不管发生什么情况都会执行,他通常用来清理任务,例如关闭网络连接等。
    一个try语句的示例:
try
{
... // 在这里写一些可能会抛出异常的语句
}
catch (ExceptionA ex)
{
... // 在这里处理一种特定类型的异常
}
catch (ExceptionB ex)
{
... // 在这里处理另一种特定类型的异常
}
finally
{
... // 清理代码
}

考虑下面这些代码:

class Test
{
static int Calc (int x) { return 10 / x; }
static void Main()
{
int y = Calc (0);
Console.WriteLine (y);
}
}

因为x是0,运行时抛出DividByZeroException异常。程序终止。可以通过catch来捕获异常来防止程序异常终止:

class Test
{
static int Calc (int x) { return 10 / x; }
static void Main()
{
try
{
int y = Calc (0);
Console.WriteLine (y);
}
catch (DivideByZeroException ex)
{
Console.WriteLine ("x cannot be zero");//这句会被输出
}
Console.WriteLine ("program completed");//这句也会被输出
}
}

提示:上面是为了阐述异常处理列举的简单例子,在实际工作中,在调用Calc()前来显示的判断被除数是否为零。异常处理需要几百个时钟周期,代价相对较高。
- 当一个异常被抛出后,CLR执行一个询问(test):当前的执行语句中是否是一个try语句并且是否可以捕获异常?(当前是否在能捕获异常的try语句块中运行?)
①如果是,则执行过程进入到catch语句中(兼容的那个,如果定义了一堆catch),如果catch顺利执行完成,则执行过程进入到try语句下面的语句(当然,如果存在finally语句块,则优先进入finally语句块中执行清理代码)。
②如果不是,执行跳转到调用函数,重复上述询问(在执行finally块之后)。如果没有用于处理异常的语句,用户最后会看到一个抛出的异常,并终止程序的运行。

catch子句

  • 一个catch字句指定了它会捕获什么类型的异常,这个异常必须是System.Exception或者其子类。
  • 捕获System.Exception可以捕获所有可能的错误,这对于下列情况来说有用:
    ①不管什么类型的异常,程序都可以修复
    ②希望重新抛出异常(在异常被计入日志后)
    ③这种方式是你的最后一种手段,在程序终止前。
    更常见的做法是为了避免处理程序没有设计到的情况,只捕获特定类型的异常。
  • 可以定义多个catch字句来捕获不同的异常:
class Test
{
static void Main (string[] args)
{
try
{
byte b = byte.Parse (args[0]);
Console.WriteLine (b);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine ("Please provide at least one argument");
}
catch (FormatException ex)
{
Console.WriteLine ("That's not a number!");
}
catch (OverflowException ex)
{
Console.WriteLine ("You've given me more than a byte!");
}
}
}

对于每一中给定的异常,只有一个catch字句会执行,但如果你想建立一张安全网去捕获更一般的异常(如System.Exception),你必须把更特殊的异常捕获catch子句放在其他catch子句的前面。

如果不需要使用变量值,也可以不给异常指定变量:

catch (StackOverflowException) // no variable
{
...
}

而且,你可以将变量个类型都省略,表示你要捕获所有的异常:

catch { ... }//没有圆括号了。。

finally子句

  • 一个finally语句块肯定会被执行,不管异常是否被抛出或者try块中的语句是否被执行完。finally语句块通常是用来清理代码的(关闭对象)。
    一个finally块也会在下面的情况下执行:
    ①在一个catch块执行完成后
    ②用一个跳转语句(例如return或goto)将控制点从try块里离开。
    ③当try块完成后。
    唯一可以“打败”finally块的是一个死循环,或者程序突然嗝儿屁了。
  • 研究下面的代码:
static void ReadFile()
{
StreamReader reader = null; // In System.IO namespace
try
{
reader = File.OpenText ("file.txt");
if (reader.EndOfStream) return;
Console.WriteLine (reader.ReadToEnd());
}
finally
{
if (reader != null) reader.Dispose();
}
}

finally块为程序添加了一种决定性的东西,在上面例子中,无论如何,steamreader总是会被关闭,就是因为在finally中写了reader.Dispose();Dispose放在在using子句中也受支持。

using语句

  • 许多类的内部都封装了非托管资源。例如文件管理,图像管理,数据库连接等,这些类都实现了System.IDisposable接口,这个接口定义了一个无参的Dispose方法。用于清除这些非托管资源。using语句定义了一种清理这些非托管资源的更优雅的方法:
using (StreamReader reader = File.OpenText ("file.txt"))
{
...
}

这个语句与下面的代码是等价的:

StreamReader reader = File.OpenText ("file.txt");
try
{
...
}
finally
{
if (reader != null)
((IDisposable)reader).Dispose();
}

在后面的笔记中我们还会更详细的介绍销毁模式。’

抛出异常

  • 可以在运行时或用户代码中抛出异常,在下面的例子中,Display抛出一个System.ArgumentNullException:
class Test
{
static void Display (string name)
{
if (name == null)
throw new ArgumentNullException ("name");
Console.WriteLine (name);
}
static void Main()
{
try { Display (null); }
catch (ArgumentNullException ex)
{
Console.WriteLine ("Caught the exception");
}
}
}
  • 重新抛出异常:可以在捕获异常后再重新抛出:
try { ... }
catch (Exception ex)
{
// Log error
...
throw; // Rethrow same exception
}

提示:在这个例子中,如果用throw ex代替throw,则虽然这个例子仍然有效,但是重新传播的异常的堆栈属性上不再反应(reflect)原始的异常信息。
此处的重新抛出异常,使记录错误后,异常不会被删除,它还用于取消本应在外部处理的异常的内部处理:

using System.Net; // (果壳中的C#第16章)
...
string s = null;
using (WebClient wc = new WebClient())
try { s = wc.DownloadString ("http://www.albahari.com/nutshell/"); }
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.NameResolutionFailure)
Console.WriteLine ("Bad domain name");
else
throw; // Can't handle other sorts of WebException, so rethrow
}
  • 另一种情形是,抛出一个明确类型的异常:
try
{
... // Parse a DateTime from XML element data
}
catch (FormatException ex)
{
throw new XmlException ("Invalid DateTime", ex);
}

(这句我理解不了了)Rethrowing a less specific exception is something you might do when crossing a trust boundary, so as not to leak technical information to potential hackers.
When rethrowing a different exception, you can set the InnerException property
with the original exception to aid debugging. Nearly all types of exceptions provide
a constructor for this purpose (such as in our example).
上面这句的中文版的翻译是:重新抛出异常不会影响异常的StackTrace属性,当重新抛出一个异常时,可以设置innerException属性的值为原始的异常,这样有利于调试,几乎所有的异常类型都可以实现这一目的。

System.Exception的关键属性

  • System.Exception类的关键属性有下面这几个:
    ①StackTrace:一个字符串, 这个字符串表示了获取调用堆栈上直接帧的字符串表示形式。
    原文是:A string representing all the methods that are called from the origin of the
    exception to the catch block.
    ②Message:一个关于错误的描述
    ③InnerException:获取导致当前异常的System.Exception实例。它本身还有另外一个innerException。
    提示:所有的C#异常都是运行时异常,没有和java对等的编译时检查异常。
  • 一些常见的异常类型:下面异常类型在C#中被广泛的使用,可以用它们来抛出异常,或者也可以将它们当作基类来派生出更多的自定义异常类型:
    ①System.ArgumentException:当使用不合适的类型的参数调用方法时抛出,这通常表宁程序有bug。
    ②System.ArgumentNullException,它是ArgumentException的子类,当传递一个null值得参数给方法时会抛出这个异常。
    ③System.ArgumentOutOfRangeException:它是ArgumentException的子类,当一个(通常是数值类型的)过大或者过小的值当作参数传递给给方法时会引发,比如说给一个只接受正数的方法传递一个负数过去。
    ④System.InvalidOperationException:不管是何种特定类型的参数值,当对象的状态不适合一个方法成功执行时抛出。例如读取一个未打开的文件或者从迭代器中读取一个列表元素在循环中被修改过的下一个值。(循环中不允许变更可枚举元素的值。)
    ⑤System.NotSupportedException:表示不支持某些功能,例如在只读的集合上调用add方法。
    ⑥System.NotImplementedException:表明某个方法还没有被实现
    ⑦System.ObjectDisposedException:表明调用的对象已经被释放。
    还有一个比较常用的异常时NullReferenceException着表示一个对象被设置为null但仍然被访问了。下面的语句会直接抛出一个NullReferenceException:
throw null;

TryXXX方法模式

当写一个方法时,你是有选择的,当错误发生时,你可以选择返回一个错误代码或者抛出一个异常,通常情况下,当错误在正常工作流之外或者你期望直接调用者不会去进行错误处理是,你可以抛出一个异常,但有时最好是给调用者提供两种选择,一个例子是int类型的Parse和TryParse方法:

public int Parse (string input);
public bool TryParse (string input, out int returnValue);

如果解析失败,Parse会抛出异常,而TryParse会返回false。
可以用XXX方法来调用TryXXX方法来实现这种模式:

public return-type XXX (input-type input)
{
return-type returnValue;
if (!TryXXX (input, out returnValue))
throw new YYYException (...)
return returnValue;
}

异常的替代品

在int.Parse中,方法可以通过返回值或者参数向调用者传递错误信息,尽管对于简单的可预见的错误,这种方式是简单的、可行的方法,但是对于所有的错误代码,它就显得捉襟见肘了,不仅会使方法的签名晦涩,也会增加不必要的复杂性,使代码混乱。它也不能推广到如运算符(例如:除法运算符)、属性等不是方法的函数上。此时可替换的方法是:把错误放在一个公共的地方,使其对调用堆栈中的所有函数都可见。但是这要求每个函数都参与错误传播模式,这本身就是冗长而且容易出错的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值