(转)异常的处理

2.1 如何针对不同的异常进行捕捉?

相信阅读本文的园友都已经养成了try-catch的习惯,但对于异常的捕捉和处理可能并不在意。确实,直接捕捉所有异常的基 类:Exception 使得程序方便易懂,但有时这样的捕捉对于业务处理没有任何帮助,对于特殊异常应该采用特殊处理能够更好地引导规划程序流程。

下面的代码演示了一个对于不同异常进行处理的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class Program
    {
        public static void Main(string[] args)
        {
            Program p = new Program();
            p.RiskWork();
 
            Console.ReadKey();
        }
 
        public void RiskWork()
        {
            try
            {
                // 一些可能会出现异常的代码
            }
            catch (NullReferenceException ex)
            {
                HandleExpectedException(ex);
            }
            catch (ArgumentException ex)
            {
                HandleExpectedException(ex);
            }
            catch (FileNotFoundException ex)
            {
                HandlerError(ex);
            }
            catch (Exception ex)
            {
                HandleCrash(ex);
            }
        }
 
        // 这里处理预计可能会发生的,不属于错误范畴的异常
        private void HandleExpectedException(Exception ex)
        {
            // 这里可以借助log4net写入日志
            Console.WriteLine(ex.Message);
        }
 
        // 这里处理在系统出错时可能会发生的,比较严重的异常
        private void HandlerError(Exception ex)
        {
            // 这里可以借助log4net写入日志
            Console.WriteLine(ex.Message);
            // 严重的异常需要抛到上层处理
            throw ex;
        }
 
        // 这里处理可能会导致系统崩溃时的异常
        private void HandleCrash(Exception ex)
        {
            // 这里可以借助log4net写入日志
            Console.WriteLine(ex.Message);
            // 关闭当前程序
            System.Threading.Thread.CurrentThread.Abort();
        }
    }

(1)如代码所示,针对特定的异常进行不同的捕捉通常很有意义,真正的系统往往要针对不同异常进行复杂的处理。异常的分别处理是一种好的编码习惯, 这要求程序员在编写代码的时候充分估计到所有可能出现异常的情况,当然,无论考虑得如何周到,最后都需要对异常的基类Exception进行捕捉,这样才 能保证所有的异常都不会被随意地抛出。

(2)除此之外,除了在必要的时候写try-catch,很多园友更推荐使用框架层面提供的异常捕捉方案,以.NET为例:

  • WinForm,可以这样写:AppDomain.CurrentDomain.UnhandledException +=new UnhandledExceptionEventHandler(UnhandledExceptionFunction);
  • ASP.NET WebForm,可以在Application_Error()方法里捕获异常
  • ASP.NET MVC,可以写ExceptionFilter
  • ASP.NET WebAPI,可以写ExceptionHandler

2.2 如何使用Conditional特性?

大家都知道,通常在编译程序时可以选择Bebug版本还是Release版本,编译器将会根据”调试“和”发布“两个不同的出发点去编译程序。在 Debug版本中,所有Debug类的断言(Assert)语句都会得到保留,相反在Release版本中,则会被通通删除。这样的机制有助于我们编写出 方便调试同时又不影响正式发布的程序代码。

But,单纯的诊断和断言可能并不能完全满足测试的需求,有时可能会需要大批的代码和方法去支持调试和测试,这个时候就需要用到 Conditional特性。Conditional特性用于编写在某个特定版本中运行的方法,通常它编写一些在Debug版本中支持测试的方法。当版本 不匹配时,编译器会把Conditional特性的方法内容置为空。

下面的一段代码演示了Conditional特性的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//含有两个成员,生日和身份证
    //身份证的第6位到第14位必须是生日
    //身份证必须是18位
    public class People
    {
        private DateTime _birthday;
        private String _id;
 
        public DateTime Birthday
        {
            set
            {
                _birthday = value;
                if (!Check())
                    throw new ArgumentException();
            }
            get
            {
                Debug();
                return _birthday;
            }
        }
 
        public String ID
        {
            set
            {
                _id = value;
                if (!Check())
                    throw new ArgumentException();
            }
            get
            {
                Debug();
                return _id;
            }
        }
 
        public People(String id, DateTime birthday)
        {
            _id = id;
            _birthday = birthday;
            Check();
            Debug();
            Console.WriteLine("People实例被构造了...");
        }
 
        // 只希望在DEBUG版本中出现
        [Conditional("DEBUG")]
        protected void Debug()
        {
            Console.WriteLine(_birthday.ToString("yyyy-MM-dd"));
            Console.WriteLine(_id);
        }
 
        //检查是否符合业务逻辑
        //在所有版本中都需要
        protected bool Check()
        {
            if (_id.Length != 18 ||
                _id.Substring(6, 8) != _birthday.ToString("yyyyMMdd"))
                return false;
            return true;
        }
    }
 
    public class Program
    {
        public static void Main(string[] args)
        {
            try
            {
                People p = new People("513001198811290215", new DateTime(1988, 11, 29));
                p.ID = "513001198811290215";
            }
            catch (ArgumentException ex)
            {
                Console.WriteLine(ex.GetType().ToString());
            }
 
            Console.ReadKey();
        }
    }

下图则展示了上述代码在Debug版本和Release版本中的输出结果:

①Debug版本:

②Release版本:

Conditional机制很简单,在编译的时候编译器会查看编译状态和Conditional特性的参数,如果两者匹配,则正常编译。否则,编译器将简单地移除方法内的所有内容。

2.3 如何避免类型转换时的异常?

我们经常会面临一些类型转换的工作,其中有些是确定可以转换的(比如将一个子类类型转为父类类型),而有些则是尝试性的(比如将基类引用的对象转换成子类)。当执行常识性转换时,我们就应该做好捕捉异常的准备。

当一个不正确的类型转换发生时,会产生InvalidCastException异常,有时我们会用try-catch块做一些尝试性的类型转换, 这样的代码没有任何错误,但是性能却相当糟糕,为什么呢?异常是一种耗费资源的机制,每当异常被抛出时,异常堆栈将会被建立,异常信息将被加载,而通常这 些工作的成本相对较高,并且在尝试性类型转换时,这些信息都没有意义。

So,在.NET中提供了另外一种语法来进行尝试性的类型转换,那就是关键字 is 和 as 所做的工作。

(1)is 只负责检查类型的兼容性,并返回结果:true 和 false。→ 进行类型判断

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void Main(string[] args)
    {
        object o = new object();
        // 执行类型兼容性检查
        if(o is ISample)
        {
            // 执行类型转换
            ISample sample = (ISample)o;
            sample.SampleShow();
        }
 
        Console.ReadKey();
    }

(2)as 不仅负责检查兼容性还会进行类型转换,并返回结果,如果不兼容则返回 null 。→ 用于类型转型

1
2
3
4
5
6
7
8
9
10
11
12
public static void Main(string[] args)
    {
        object o = new object();
        // 执行类型兼容性检查
        ISample sample = o as ISample;
        if(sample != null)
        {
            sample.SampleShow();
        }
 
        Console.ReadKey();
    }

两者的共同之处都在于:不会抛出异常!综上比较,as 较 is 在执行效率上会好一些,在实际开发中应该量才而用,在只进行类型判断的应用场景时,应该多使用 is 而不是 as。

posted @ 2016-02-16 17:23  肥大头  阅读(213)  评论(0编辑  收藏  举报