异常处理、性能、建议

异常处理的作用

无论编码技术有多好,程序都必须处理任何可能出现的错误。如果应用程序不对异常进行任何处理,直接抛出最终会导致应用程序退出。为了提高程序的健壮性,必须对任何异常进行必要的处理。

异常执行流程

异常处理的关键:

1、  定义什么错误

2、  判断错误

3、  从错误中恢复

如果在try块中抛出一个异常,CLR将搜索捕捉类型与异常类型相同的catch块,如果没有找到,CLR会调用栈的更高一层搜索与异常类型匹配的捕捉类型,如果到了栈的顶部还未找到,就会发生一个未处理异常。一旦找到一个匹配的catch块,所有内层的finally块会被执行;如果没有找到,内层的finally块是不会执行的,这一点要特别注意。接着执行匹配catch块内容,最后是该catch块对应的finally块(如果有的话)。

下面代码展现了异常处理的关键流程。

private void SomeMethod()
        {
            try
            {
                //可能会抛异常的代码放在这里
            }
            catch (InvalidOperationException)
            {
                //从InvalidOperationException恢复的代码放在这里
            }
            catch (IOException)
            {
                //从IOException恢复的代码放在这里
            }
            catch
            {
                //从除上面的异常外的其他异常恢复的代码放在这里
                ////捕捉到任何异常,通常要重新抛出异常。
                throw;
            }
            finally
            {
                //这里的代码总是执行,对始于try块的任何操作进行清理
                //这里的代码总是执行
            }
            // 如果try块没有抛出异常,或异常被捕获后没有抛出或没有重新抛出异
     //常,就执行这里的代码。 }

 

  下面代码展现三种抛出异常方式对性能的影响 

View Code
 internal class Program
    {
        private static void Main(string[] args)
        {

            int times = 3000;
            string test = "test";
            int a = 0;
            Stopwatch stopwatch;

            //TryParse模式
            stopwatch = Stopwatch.StartNew();
            for (int i = 0; i < times; i++)
            {
                Int32.TryParse(test, out a);
            }
            stopwatch.Stop();
            Console.WriteLine("TryParse模式避免异常:" + stopwatch.ElapsedMilliseconds);

            //抛出指定的异常实例
            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < times; i++)
            {
                try
                {
                    if (!int.TryParse(test, out a))
                    {
                        throw new ArgumentException(test);
                    }
                }
                catch
                {

                }
            }
            stopwatch.Stop();
            Console.WriteLine("抛出指定的异常:" + stopwatch.ElapsedMilliseconds);

            //在嵌套层里面不做任何异常处理直接往顶层抛出
            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < times; i++)
            {
                try
                {
                    MyClass1.Method1();
                }
                catch  
                {
                }
            }
            stopwatch.Stop();
            Console.WriteLine("在嵌套层里面不做任何异常处理直接往顶层抛出:" + stopwatch.ElapsedMilliseconds);

            // 嵌套层已处理异常,得体恢复返回
            stopwatch.Reset();
            stopwatch.Start();
            for (int i = 0; i < times; i++)
            {
                MyClass4.Method4();
            }
            stopwatch.Stop();
            Console.WriteLine("嵌套层已处理异常,得体恢复返回:" + stopwatch.ElapsedMilliseconds);
            Console.ReadLine();

        }
    }

    internal class MyClass1
    {
        public static void Method1()
        {
            MyClass2.Method2();
        }
    }

    internal class MyClass2
    {
        public static void Method2()
        {
            MyClass3.Method3();
        }
    }

    internal class MyClass3
    {
        public static void Method3()
        {
            int.Parse("test");
        }
    }


    internal class MyClass4
    {
        public static void Method4()
        {
            MyClass5.Method5();
        }
    }

    internal class MyClass5
    {
        public static void Method5()
        {
            MyClass6.Method6();
        }
    }

    internal class MyClass6
    {
        public static string Method6()
        {
            string a = "";
            try
            {
                int.Parse("test");
            }
            catch  
            {
                a = "";
            }
            return a;
        }
    }

 

运行结果:

 

 从运行结果来看指定抛出异常性能比直接抛异常好一点,为什么会这样?请拍砖。在嵌套层里面进行异常处理与直接往顶层抛异常的性能相差不大,

但最重要的地方是对错误进行恢复。异常处理的重点是错误的恢复、处理。

 

微软对异常处理方式建议

对于可能在常见方案中引发异常的成员,可以考虑使用 Tester-Doer 模式来避免与异常相关的性能问题。

例如,对于函数的参数问题,就必须创建检测类检测输入是否正确。对于可能在常见方案中引发异常的成员,可以考虑使用 TryParse 模式来避免与异常相关的性能问题。

TryParse模式参考int.TryParse等.就是创建正常运行的条件避免抛出异常.

对于可能在常见方案中引发异常的成员,可以考虑使用 Tester-Doer 模式来避免与异常相关的性能问题。

Tester-doer 模式将分为了调用,可能会引发异常,分成两个部分: 一种测试仪和实干家。 Tester 对可能导致 Doer 引发异常的状态执行测试。 测试恰好插入在引发异常的代码之前,从而防范异常发生。

下面的代码示例演示此模式的 Doer 部分。 该示例包含一个方法,在向该方法传递 null(在 Visual Basic 中为Nothing)值时该方法将引发异常。 如果频繁地调用该方法,就可能会对性能造成不良影响。

View Code
public class Doer
{
    // Method that can potential throw exceptions often.
    public static void ProcessMessage(string message)
    {
        if (message == null)
        {
            throw new ArgumentNullException("message");
        }
    }
    // Other methods...
}

 

  下面的代码示例演示此模式的 Tester 部分。 该方法利用一个测试来避免在 Doer 将引发异常时调用 Doer (ProcessMessage)。

View Code
public class Tester
{
    public static void TesterDoer(ICollection<string> messages)
    {
        foreach (string message in messages)
        {
            // Test to ensure that the call
            // won't cause the exception.
            if (message != null)
            {
                Doer.ProcessMessage(message);
            }
        }
    }
}


对于可能在常见方案中引发异常的成员,可以考虑使用 TryParse 模式来避免与异常相关的性能问题。若要实现 TryParse 模式,

需要为执行可在常见方案中引发异常的操作提供两种不同的方法。 第一种方法 X, 执行该操作并在适当时引发异常。

第二种方法 TryX, 不引发异常,而是返回一个 Boolean 值以指示成功还是失败。

由对 TryX 的成功调用所返回的任何数据都通过使用 out(在 Visual Basic 中为 ByRef)参数予以返回。Parse 和 TryParse 方法就是此模式的示例。

为每个使用 TryParse 模式的成员提供一个引发异常的成员。只提供 TryX 方法几乎在任何时候都不是正确的设计,因为使用该方法需要了解 out 参数。

此外,对于大多数常见方案来说,

异常对性能的影响不会构成问题;因此应在大多数常见方案中提供易于使用的方法。

 结论: 由微软提供的Doer-Tester模式可知处理异常的方式最好是不要引发异常,在可能抛出异常的地方进行必要的检查和验证。例如对实参进行验证。

总结:实现自己的方法时,如果方法无法完成方法名所指明的任务,就应该抛出异常或对错误进行得体恢复。抛出异常时应该详细说明方法为什么无法完成任务。对于未指定处理的异常通常被写入日志,开发人员必须修复该异常所在的bug。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章