• 【C#】详解C#异常


    目录结构:

    contents structure [+]

    在这篇文章中,笔者会阐述C#中的异常。C#是一门面向对象的语言,面向对象编程极大的提高了开发人员的效率。
    比如:

    Boolean b= "JReE".Substring(1, 1).ToUpper().EndsWith("E");//true

    很容易书写这行代码,也很容易维护和阅读。但是这有一个前提,就是不发生错误,但是错误总是存在的。.NET Framework通过异常来处理这个问题。

    1.异常处理机制

    以下C#代码展示了异常处理的标准用法:

    public void SomeMethod() {
        try
        {
            //需要得体的进行恢复/清理的代码放在这里
        }
        catch (InvalidOperationException e)
        {
            //从InvalidOperationException恢复的代码放在这里
        }
        catch (IOException e)
        {
            //从IOException恢复的代码放在这里
        }
        catch
        {
            //除了上述异常的所有异常恢复都放在这里
            ...
            //通常情况下,若什么异常都捕捉,那么需要重新抛出异常
            throw;
        }
        finally {
            //对try块中的使用到的资源进行清理
            //这里的代码总是执行
        }
        //如果try块没有抛出异常,或是某个catch块捕捉到异常,但没有抛出,那么执行以下的代码
        ...
    }

    1.1 try块

    如果代码需要执行一般性的恢复操作,需要进行资源清理,或者两者都需要。那么就应该放到try块中,然后从catch块中恢复,从finally块中清理资源。
    C#的编译器不会强制要求进行异常捕获,这一点没有java支持的好。
    例如:

    static void SomeMethod() {
        throw new InvalidCastException("类型转化失败");
    }

    在这里定义了一个SomeMethod方法,调用者可以直接使用SomeMethod方法,不需要进行捕获,可以正常通过编译。通常情况下,可以为方法添加注释,在Visual Studio开发时候就可以提醒开发人员,该方法抛出那些异常。

    /// <summary>
    /// 方法测试
    /// </summary>
    /// <exception cref="System.InvalidCastException">类型转化失败时抛出</exception>
    static void SomeMethod() {
        throw new InvalidCastException("类型转化失败");
    }

    如果你没有注释方法的异常信息,那么你代码的调用者就不知道你的代码可能会抛出那些异常,就不能针对性地从异常中恢复。
    开发人员若是不知道这些信息,为了增强程序的健壮性,就会使用catch(Exception e)的语言,但这样的语句在开发中是应该避免的。

    1.2 catch块

    catch块中是响应异常需要执行的代码。一个try块可以关联至少0个catch块。如果try块中的代码抛出执行异常,那么不会执行catch块。catch关键值里面圆括号的表达式被称为捕捉类型,C#要求所以的异常类型必须是System.Exception派生的。

    1.3 finally块

    finally块中的代码是保证是会执行的。一般在finnaly块中执行try块所需要执行的资源清理操作。
    例如:

        FileStream file = null;
        try
        {
            file = new FileStream("test.txt", FileMode.Open);
        }
        catch (IOException e)
        {
            //执行从IOException恢复的操作
        }
        finally {
            //执行资源清理
            if (file != null) {
                file.Close();
            }
        }

    2.自定义异常

    C#的规范规定System.Exception是所有异常的基类,然后System.ApplicationException和System.SystemException直接从System.Exception派生,另外,System.ApplicationException是所有用户自定义异常的基类,System.SystemException是所有系统异常类的基类。
    但这一规定并没有得到很好的执行,比如System.InvalidTimeZoneException直接派生于System.Exception类,像这种"不守规范"的异常类还有很多,所以说System.ApplicationException和System.SystemException类型没有什么特殊的含义了。
    自定义异常建议直接从System.Exception派生,设计自己的异常通常是一件比较繁琐的事,因为从System.Exception派生异常都应该是序列化的。

    /// <summary>
    /// 定义一个泛型异常类,该类主要用于完成序列化操作
    /// </summary>
    /// <typeparam name="TExceptionArgs"></typeparam>
    [Serializable]
    public sealed class Exception<TExceptionArgs> : Exception
    where TExceptionArgs : ExceptionArgs
    {
    private const String c_args = "Args";//用作(反)序列化的key
    private readonly TExceptionArgs m_args = null;
    
    public Exception(String Message = null, Exception InnerException = null):
        this(null,Message,InnerException) {
    }
    public Exception(TExceptionArgs m_args = null, String Message = null, Exception InnerException = null) :
        base(Message,InnerException){
        this.m_args = m_args;
    }
    /// <summary>
    /// 该构造器用于反序列化,如果该类是密封的,那么该构造器应该是私有的。
    /// 如果该类不是密封的,那么该构造器应该是受保护的。
    /// </summary>
    /// <param name="info"></param>
    /// <param name="context"></param>
    [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
    private Exception(SerializationInfo info, StreamingContext context):
        base(info,context)
    {
        m_args = (TExceptionArgs)info.GetValue(c_args, typeof(TExceptionArgs));
    }
    /// <summary>
    /// 该方法用于序列化
    /// </summary>
    /// <param name="info"></param>
    /// <param name="context"></param>
    [SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue(c_args,m_args);
        base.GetObjectData(info,context);
    }
    public override string Message {
        get {
            String baseMsg = base.Message;
            return String.IsNullOrEmpty(m_args.Message) ? baseMsg : baseMsg + "("+m_args.Message+")";
        }
    }
    public override Boolean Equals(Object obj)
    {
        Exception<TExceptionArgs> other = obj as Exception<TExceptionArgs>;
        if (other == null) {
            return false;
        }
        return Object.Equals(m_args, other.m_args) && base.Equals(obj);
    }
    public override int GetHashCode()
    {
        return base.GetHashCode();
    }
    }
    Exception<TExceptionArgs>.csc

    然后定义约束类ExceptionArgs:

    [Serializable]
    public abstract class ExceptionArgs{
    public virtual String Message {
        get {
            return String.Empty;
        }
    }
    }
    ExceptionArgs.csc

    有了上面两个类,然后再来定义其他类型的类就方便了。下面定义一个代表磁盘已满的类,

    /// <summary>
    /// 代表磁盘已满的异常类
    /// </summary>
    [Serializable]
    public class DiskFullExceptionArgs : ExceptionArgs {
    private readonly String m_diskpath;
    
    public DiskFullExceptionArgs(String m_diskpath)
    {
        this.m_diskpath = m_diskpath;
    }
    
    public override string Message
    {
        get
        {
            return m_diskpath == null ? base.Message : "Diskpath="+m_diskpath;
        }
    }
    }

    然后可以使用如下的代码来调用:

    try {
        throw new Exception<DiskFullExceptionArgs>(new DiskFullExceptionArgs(@"c:"), "disk is full");
    }catch(Exception e){
        Console.WriteLine(e.Message);
    }

    3.CLS异常和非CLS异常

    在这里首先铺垫一张图片,介绍一下CLS和CLR的关系:

    从图中我们可以清楚知道CLR和CLS的关系,CLS的全称是公共语言规范(Common Language Specification)。


    所有面向CLR的编程语言都必须支持从Exception派生的异常对象,这是CLS的对此的硬性规定。但是CLR实际上允许抛出任何类型的实例异常,而且有些编程语言运行代码抛出非CLS相容的异常,但C#编译器只允许抛出从Exception派生的异常类型,


    在CLR2.0以前,程序员写catch块来捕捉异常时,只能捕捉与CLS相容的异常。如果用C#方法调用了另一种编程语言的代码,然后另一种编程语言的代码抛出一种非CLS相容的异常,那么C#代码更本不能捕捉这个异常。在CLR2.0的版本中,Microsoft引入了一种System.Runtime.CompilerServices.RuntimeWrappedException类,该派生自Exception类,所以该类是一个CLS相容的异常类型。在CLR2.0中,非CLS相容的异常类型抛出的时候,CLR会自动构建一个RuntimeWrappedException实例,并且初始化该类的实例,使其应用实际抛出的对象。


    关于CLR的版本,可以在%SystemRoot%Microsoft.NET目录下查看。


    这样就CLR就可以把非CLS异常转化为CLS异常了。现在看一看捕捉非CLS的形式代码:

        try
        {
            //需要得体的进行恢复或清理的代码
        }
        catch (Exception e)
        {
            //在CLR2.0以前,这个块只能捕捉与CLS相容的异常
            //在CLR2.0及以后,这个块能捕捉与CLS相容和CLS不相容的异常
            throw;//重新抛出异常
        }
        catch {
            //在CLR所有版本中,这个块都能捕捉与CLS相容和CLS不相容的异常
            throw;//重新抛出异常
        }

    为了进行验证,笔者使用C++来抛出一个非CLS异常,定义一个文件。
    ClassLibrary1.h文件

    namespace ClassLibrary1 {
        public ref class MyClass
        {
            public:double division(int a,int b){
                   if(b==0){
                       throw "Division by zero condition!";//抛出一个非CLS的异常
                   }
                   return (a/b);
            }
        };
    }

    定义了一个MyClass类,然后类中定义了division方法,若除数为0,那么抛出一个字符串异常“Division by zero condition!”。
    接下来编译为ClassLibrary1.dll文件,并且在项目中引入该文件,然后使用如下的代码调用:

        MyClass myClass=new  MyClass();
        try
        {
            double res = myClass.division(2, 0);
            Console.WriteLine(res);
        }
        catch (Exception e)
        {
            Console.WriteLine(e.GetType() + "
    ");//System.Runtime.InteropServices.SEHException
            Console.WriteLine(e.Message + "
    ");//外部组件发生异常。
        }
  • 相关阅读:
    数据库操作顺序
    数据库不允许远程连接
    redis-操作
    flask源码系列之-wtforms
    MySQL的btree索引和hash索引的区别
    HDU 1242
    HDU 1241
    HDU 1240
    HDU 1010
    Codeforces Round #339 (Div. 2) A
  • 原文地址:https://www.cnblogs.com/HDK2016/p/9058649.html
Copyright © 2020-2023  润新知