当前位置: 移动技术网 > IT编程>开发语言>.net > 关于.NET异常处理的思考总结

关于.NET异常处理的思考总结

2017年12月12日  | 移动技术网IT编程  | 我要评论

中国成第二投资国,教子恩返,僵尸奶奶再战戈师奶

年关将至,对于大部分程序员来说,马上就可以闲下来一段时间了,然而在这个闲暇的时间里,唯有争论哪门语言更好可以消磨时光,估计最近会有很多关于java与.net的博文出现,我表示要作为一个吃瓜群众,静静的看着大佬们发表心情。

以上的废话说的够多了,这里就不再废话了,还是切入正题吧。

在项目开发中,对于系统和代码的稳定性和容错性都是有对应的要求。实际开发项目中的代码与样例代码的区别,更多的是在代码的运行的稳定性、容错性、扩展性的比较。因为对于实现一个功能来说,实现功能的核心代码是一样的,可能只是在写法上优化而已,但是在实现某一个操作上使用的类来说,这一点是绝大多数时候是一样的。这样看来,我们在实际开发的过程中,需要考虑的问题比较多,已经不仅仅局限于某一具体的功能实现,更多的是代码的稳定性和扩展性考虑。

以上是在实际开发中需要面对的问题,笔者在最近的博文中,也在考虑这个异常到底需要怎么去写,以及异常到底需要怎么去理解,希望对大家有一个帮助,也欢迎大家提出自己的想法和意见,分享自己的知识和见解。

一.dotnet异常的概述:

谈到异常,我们就需要知道什么叫做异常,万事万物如果我们想去学习,就应该知道我们要学习的东西是什么,这样在心里也好有一个大概的认知。异常是指成员没有完成它的名称宣称可以完成的行动。在.net中,构造器、获取和设置属性、添加和删除事件、调用操作符重载和调用转换操作符等等都没有办法返回错误代码,但是在这些构造中又需要报告错误,那就必须提供异常处理机制。

在异常的处理中,我们经常使用到的三个块分别是:try块;catch块;finally块。这三个块可以一起使用,也可以不写catch块使用,异常处理块可以嵌套使用,具体的方法在下面会介绍到。

在异常的处理机制中,一般有三种选择:重新抛出相同的异常,向调用栈高一层的代码通知该异常的发生;抛出一个不同的异常,想调用栈高一层代码提供更丰富的异常信息;让线程从catch块的底部退出。  

有关异常的处理方式,有一些指导性的建议。

1.恰当的使用finally块:

finally块可以保证不管线程抛出什么类型的异常都可以被执行,finall块一般用来做清理那些已经成功启动的操作,然后再返回调用者或者finally块之后的代码。

2.异常捕捉需适当:

为什么要适当的捕捉异常呢?如下代码,因为我们不能什么异常都去捕捉,在捕获异常后,我们需要去处理这些异常,如果我们将所有的异常都捕捉后,但是没有预见会发生的异常,我们就没有办法去处理这些异常。

如果应用程序代码抛出一个异常,应用程序的另一端则可能预期要捕捉这个异常,因此不能写成一个”大小通吃“的异常块,应该允许该异常在调用栈中向上移动,让应用程序代码针对性地处理这个异常。

在catch块中,可以使用system.exception捕捉异常,但是最好在catch块末尾重新抛出异常。至于原因在后面会讲解到。

   try
   {
    var hkml = getregistrykey(rootkey);
    var subkey = hkml.createsubkey(subkey);
    if (subkey != null && keyname != string.empty)
     subkey.setvalue(keyname, keyvalue, registryvaluekind.string);
   }
   catch (exception ex)
   {
    log4helper.error("创建注册表错误" + ex);
    throw new exception(ex.message,ex);
   }

3.从异常中恢复:

我们在捕获异常后,可以针对性的写一些异常恢复的代码,可以让程序继续运行。在捕获异常时,需要捕获具体的异常,充分的掌握在什么情况下会抛出异常,并知道从捕获的异常类型派生出了那些类型。除非在catch块的末尾重新抛出异常,否则不要处理或捕获system.exception异常。

4.维持状态:

一般情况下,我们完成一个操作或者一个方法时,需要调用几个方法组合完成,在执行的过程中会出现前面几个方法完成,后面的方法发生异常。发生不可恢复的异常时回滚部分完成的操作,因为我们需要恢复信息,所有我们在捕获异常时,需要捕获所有的异常信息。

5.隐藏实现细节来维持契约:

有时可能需要捕捉一个异常并重新抛出一个不同的异常,这样可以维系方法的契约,抛出的心异常类型地应该是一个具体的异常。看如下代码:

filestream fs = null;
   try
   {
    fs = filestream();
    
   }
   catch (filenotfoundexception e)
   {
          //抛出一个不同的异常,将异常信息包含在其中,并将原来的异常设置为内部异常
    throw new namenotfoundexception();
   }
   catch (ioexception e)
   {
 
    //抛出一个不同的异常,将异常信息包含在其中,并将原来的异常设置为内部异常
    throw new namenotfoundexception(); 
   } 
   finally 
   {
    if (fs != null) 
    { 
    fs.close(); 
   } 
   }

以上的代码只是在说明一种处理方式。应该让抛出的所有异常都沿着方法的调用栈向上传递,而不是把他们”吞噬“了之后抛出一个新的异常。如果一个类型构造器抛出一个异常,而且该异常未在类型构造器方法中捕获,clr就会在内部捕获该异常,并改为抛出一个新的typeinitialztionexception。

二.dotnet异常的常用处理机制:

在代码发生异常后,我们需要去处理这个异常,如果一个异常没有得到及时的处理,clr会终止进程。在异常的处理中,我们可以在一个线程捕获异常,在另一个线程中重新抛出异常。异常抛出时,clr会在调用栈中向上查找与抛出的异常类型匹配的catch块。如果没有任何catch块匹配抛出的异常类型,就发生一个未处理异常。clr检测到进程中的任何线程有一个位处理异常,都会终止进程。

1.异常处理块:

(1).try块:包含代码通常需要执行一些通用的资源清理操作,或者需要从异常中恢复,或者两者都需要。try块还可以包含也许会抛出异常的代码。一个try块至少有一个关联的catch块或finall块。      

(2).catch块:包含的是响应一个异常需要执行的代码。catch关键字后的圆括号中的表达式是捕获类型。捕获类型从system.exception或者其派生类指定。clr自上而下搜素一个匹配的catch块,所以应该教具体的异常放在顶部。一旦clr找到一个具有匹配捕获类型的catch块,就会执行内层所有finally块中的代码,”内层finally“是指抛出异常的tey块开始,到匹配异常的catch块之间的所有finally块。

使用system.exception捕捉异常后,可以采用在catch块的末尾重新抛出异常,因为如果我们在捕获exception异常后,没有及时的处理或者终止程序,这一异常可能对程序造成很大的安全隐患,exception类是所有异常的基类,可以捕获程序中所有的异常,如果出现较大的异常,我们没有及时的处理,造成的问题是巨大的。

(3).finally块:包含的代码是保证会执行的代码。finally块的所有代码执行完毕后,线程退出finally块,执行紧跟在finally块之后的语句。如果不存在finally块,线程将从最后一个catch块之后的语句开始执行。

备注:异常块可以组合和嵌套,对于三个异常块的样例,在这里就不做介绍,异常的嵌套可以防止在处理异常的时候再次出现未处理的异常,以上这些就不再赘述。

2.异常处理实例:

(1).异常处理扩展方法:

  /// <summary>
  /// 格式化异常消息
  /// </summary>
  /// <param name="e">异常对象</param>
  /// <param name="ishidestacktrace">是否隐藏异常规模信息</param>
  /// <returns>格式化后的异常信息字符串</returns>
  public static string formatmessage(this exception e, bool ishidestacktrace = false)
  {
   var sb = new stringbuilder();
   var count = 0;
   var appstring = string.empty;
   while (e != null)
   {
    if (count > 0)
    {
     appstring += " ";
    }
    sb.appendline(string.format("{0}异常消息:{1}", appstring, e.message));
    sb.appendline(string.format("{0}异常类型:{1}", appstring, e.gettype().fullname));
    sb.appendline(string.format("{0}异常方法:{1}", appstring, (e.targetsite == null ? null : e.targetsite.name)));
    sb.appendline(string.format("{0}异常源:{1}", appstring, e.source));
    if (!ishidestacktrace && e.stacktrace != null)
    {
     sb.appendline(string.format("{0}异常堆栈:{1}", appstring, e.stacktrace));
    }
    if (e.innerexception != null)
    {
     sb.appendline(string.format("{0}内部异常:", appstring));
     count++;
    }
    e = e.innerexception;
   }
   return sb.tostring();
  }

 (2).验证异常:

  /// <summary>
  /// 检查字符串是空的或空的,并抛出一个异常
  /// </summary>
  /// <param name="val">值测试</param>
  /// <param name="paramname">参数检查名称</param>
  public static void checknullorempty(string val, string paramname)
  {
   if (string.isnullorempty(val))
    throw new argumentnullexception(paramname, "value can't be null or empty");
  }

  /// <summary>
  /// 请检查参数不是空的或空的,并抛出异常
  /// </summary>
  /// <param name="param">检查值</param>
  /// <param name="paramname">参数名称</param>
  public static void checknullparam(string param, string paramname)
  {
   if (string.isnullorempty(param))
    throw new argumentnullexception(paramname, paramname + " can't be neither null nor empty");
  }

  /// <summary>
  /// 检查参数不是无效,并抛出一个异常
  /// </summary>
  /// <param name="param">检查值</param>
  /// <param name="paramname">参数名称</param>
  public static void checknullparam(object param, string paramname)
  {
   if (param == null)
    throw new argumentnullexception(paramname, paramname + " can't be null");
  }

  /// <summary>
  /// 请检查参数1不同于参数2
  /// </summary>
  /// <param name="param1">值1测试</param>
  /// <param name="param1name">name of value 1</param>
  /// <param name="param2">value 2 to test</param>
  /// <param name="param2name">name of vlaue 2</param>
  public static void checkdifferentsparams(object param1, string param1name, object param2, string param2name)
  {
   if (param1 == param2) {
    throw new argumentexception(param1name + " can't be the same as " + param2name,
     param1name + " and " + param2name);
   }
  }

  /// <summary>
  /// 检查一个整数值是正的(0或更大)
  /// </summary>
  /// <param name="val">整数测试</param>
  public static void positivevalue(int val)
  {
   if (val < 0)
    throw new argumentexception("the value must be greater than or equal to 0.");
  }

(3).try-catch扩展操作:

  /// <summary>
  ///  对某对象执行指定功能与后续功能,并处理异常情况
  /// </summary>
  /// <typeparam name="t">对象类型</typeparam>
  /// <param name="source">值</param>
  /// <param name="action">要对值执行的主功能代码</param>
  /// <param name="failureaction">catch中的功能代码</param>
  /// <param name="successaction">主功能代码成功后执行的功能代码</param>
  /// <returns>主功能代码是否顺利执行</returns>
  public static bool trycatch<t>(this t source, action<t> action, action<exception> failureaction,
   action<t> successaction) where t : class
  {
   bool result;
   try
   {
    action(source);
    successaction(source);
    result = true;
   }
   catch (exception obj)
   {
    failureaction(obj);
    result = false;
   }
   return result;
  }

  /// <summary>
  ///  对某对象执行指定功能,并处理异常情况
  /// </summary>
  /// <typeparam name="t">对象类型</typeparam>
  /// <param name="source">值</param>
  /// <param name="action">要对值执行的主功能代码</param>
  /// <param name="failureaction">catch中的功能代码</param>
  /// <returns>主功能代码是否顺利执行</returns>
  public static bool trycatch<t>(this t source, action<t> action, action<exception> failureaction) where t : class
  {
   return source.trycatch(action,
    failureaction,
    obj => { });
  }

  /// <summary>
  ///  对某对象执行指定功能,并处理异常情况与返回值
  /// </summary>
  /// <typeparam name="t">对象类型</typeparam>
  /// <typeparam name="tresult">返回值类型</typeparam>
  /// <param name="source">值</param>
  /// <param name="func">要对值执行的主功能代码</param>
  /// <param name="failureaction">catch中的功能代码</param>
  /// <param name="successaction">主功能代码成功后执行的功能代码</param>
  /// <returns>功能代码的返回值,如果出现异常,则返回对象类型的默认值</returns>
  public static tresult trycatch<t, tresult>(this t source, func<t, tresult> func, action<exception> failureaction,
   action<t> successaction)
   where t : class
  {
   tresult result;
   try
   {
    var u = func(source);
    successaction(source);
    result = u;
   }
   catch (exception obj)
   {
    failureaction(obj);
    result = default(tresult);
   }
   return result;
  }

  /// <summary>
  ///  对某对象执行指定功能,并处理异常情况与返回值
  /// </summary>
  /// <typeparam name="t">对象类型</typeparam>
  /// <typeparam name="tresult">返回值类型</typeparam>
  /// <param name="source">值</param>
  /// <param name="func">要对值执行的主功能代码</param>
  /// <param name="failureaction">catch中的功能代码</param>
  /// <returns>功能代码的返回值,如果出现异常,则返回对象类型的默认值</returns>
  public static tresult trycatch<t, tresult>(this t source, func<t, tresult> func, action<exception> failureaction)
   where t : class
  {
   return source.trycatch(func,
    failureaction,
    obj => { });
  }

本文没有具体介绍try,catch,finally的使用,而是给出一些比较通用的方法,主要是一般的开发者对于三个块的使用都有一个认识,就不再做重复的介绍。

三.dotnet的exception类分析:

clr允许异常抛出任何类型的实例,这里我们介绍一个system.exception类:

1.message属性:指出抛出异常的原因。

[__dynamicallyinvokable]
public virtual string message
{
 [__dynamicallyinvokable]
 get
 {
  if (this._message != null)
  {
   return this._message;
  }
  if (this._classname == null)
  {
   this._classname = this.getclassname();
  }
  return environment.getruntimeresourcestring("exception_wasthrown", new object[] { this._classname });
 }
}

由以上的代码可以看出,message只具有get属性,所以message是只读属性。getclassname()获取异常的类。getruntimeresourcestring()获取运行时资源字符串。

2.stacktrace属性:包含抛出异常之前调用过的所有方法的名称和签名。

public static string stacktrace
{
 [securitysafecritical]
 get
 {
  new environmentpermission(permissionstate.unrestricted).demand();
  return getstacktrace(null, true);
 }
}

environmentpermission()用于环境限制,permissionstate.unrestricted设置权限状态,getstacktrace()获取堆栈跟踪,具体看一下getstacktrace()的代码。

internal static string getstacktrace(exception e, bool needfileinfo)
{
 stacktrace trace;
 if (e == null)
 {
  trace = new stacktrace(needfileinfo);
 }
 else
 {
  trace = new stacktrace(e, needfileinfo);
 }
 return trace.tostring(stacktrace.traceformat.normal);
}
public stacktrace(exception e, bool fneedfileinfo)
{
 if (e == null)
 {
  throw new argumentnullexception("e");
 }
 this.m_inumofframes = 0;
 this.m_imethodstoskip = 0;
 this.capturestacktrace(0, fneedfileinfo, null, e);
}

 以上是获取堆栈跟踪方法的具体实现,此方法主要用户调试的时候。

3.getbaseexception()获取基础异常信息方法。

[__dynamicallyinvokable]
public virtual exception getbaseexception()
{
 exception innerexception = this.innerexception;
 exception exception2 = this;
 while (innerexception != null)
 {
  exception2 = innerexception;
  innerexception = innerexception.innerexception;
 }
 return exception2;
}

innerexception属性是内在异常,这是一个虚方法,在这里被重写。具体看一下innerexception属性。

[__dynamicallyinvokable]
public exception innerexception
{
 [__dynamicallyinvokable, targetedpatchingoptout("performance critical to inline this type of method across ngen image boundaries")]
 get
 {
  return this._innerexception;
 }
}

 4.tostring()将异常信息格式化。

private string tostring(bool needfilelineinfo, bool needmessage)
{
 string classname;
 string str = needmessage ? this.message : null;
 if ((str == null) || (str.length <= 0))
 {
  classname = this.getclassname();
 }
 else
 {
  classname = this.getclassname() + ": " + str;
 }
 if (this._innerexception != null)
 {
  classname = classname + " ---> " + this._innerexception.tostring(needfilelineinfo, needmessage) + environment.newline + " " + environment.getruntimeresourcestring("exception_endofinnerexceptionstack");
 }
 string stacktrace = this.getstacktrace(needfilelineinfo);
 if (stacktrace != null)
 {
  classname = classname + environment.newline + stacktrace;
 }
 return classname;
}

在此方法中,将获取的异常信息进行格式化为字符串,this.getclassname() 获取异常类的相关信息。

以上我们注意到[__dynamicallyinvokable]定制属性,我们看一下具体的实现代码:

[targetedpatchingoptout("performance critical to inline this type of method across ngen image boundaries")]
public __dynamicallyinvokableattribute()
{
}

以上我们主要注释部分,”图像边界“这个属性的相关信息,请参见《via clr c#》,这里就不做具体的介绍。

四.总结:

以上在对异常的介绍中,主要介绍了clr的异常处理机制,一些较为通用的异常代码,以及对exception类的介绍。在实际的项目中,我们一般不要将异常直接抛出给客户,我们在编写程序时,已经考虑程序的容错性,在程序捕获到异常后,尽量去恢复程序,或者将异常信息写入日志,让程序进入错误页。如果出现比较严重的异常,最后将异常抛出,终止程序。

希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网