溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何解決.NET的異常處理

發(fā)布時(shí)間:2021-09-16 11:14:53 來(lái)源:億速云 閱讀:108 作者:柒染 欄目:開(kāi)發(fā)技術(shù)

本篇文章為大家展示了如何解決.NET的異常處理,內(nèi)容簡(jiǎn)明扼要并且容易理解,絕對(duì)能使你眼前一亮,通過(guò)這篇文章的詳細(xì)介紹希望你能有所收獲。

在項(xiàng)目開(kāi)發(fā)中,對(duì)于系統(tǒng)和代碼的穩(wěn)定性和容錯(cuò)性都是有對(duì)應(yīng)的要求。實(shí)際開(kāi)發(fā)項(xiàng)目中的代碼與樣例代碼的區(qū)別,更多的是在代碼的運(yùn)行的穩(wěn)定性、容錯(cuò)性、擴(kuò)展性的比較。因?yàn)閷?duì)于實(shí)現(xiàn)一個(gè)功能來(lái)說(shuō),實(shí)現(xiàn)功能的核心代碼是一樣的,可能只是在寫法上優(yōu)化而已,但是在實(shí)現(xiàn)某一個(gè)操作上使用的類來(lái)說(shuō),這一點(diǎn)是絕大多數(shù)時(shí)候是一樣的。這樣看來(lái),我們?cè)趯?shí)際開(kāi)發(fā)的過(guò)程中,需要考慮的問(wèn)題比較多,已經(jīng)不僅僅局限于某一具體的功能實(shí)現(xiàn),更多的是代碼的穩(wěn)定性和擴(kuò)展性考慮。

 以上是在實(shí)際開(kāi)發(fā)中需要面對(duì)的問(wèn)題,筆者在最近的博文中,也在考慮這個(gè)異常到底需要怎么去寫,以及異常到底需要怎么去理解,在博文中,也有不少的園友對(duì)異常的寫法和處理提出了自己的意見(jiàn),在這里我就寫一下自己的一些理解,可能寫的比較淺顯和粗略,但是只當(dāng)是一個(gè)引子,可以引出大佬們來(lái)談?wù)勛约旱膶?shí)際項(xiàng)目經(jīng)驗(yàn)。希望對(duì)大家有一個(gè)幫助,也歡迎大家提出自己的想法和意見(jiàn),分享自己的知識(shí)和見(jiàn)解。

一.DotNET異常的概述:

談到異常,我們就需要知道什么叫做異常,萬(wàn)事萬(wàn)物如果我們想去學(xué)習(xí),就應(yīng)該知道我們要學(xué)習(xí)的東西是什么,這樣在心里也好有一個(gè)大概的認(rèn)知。異常是指成員沒(méi)有完成它的名稱宣稱可以完成的行動(dòng)。在.NET中,構(gòu)造器、獲取和設(shè)置屬性、添加和刪除事件、調(diào)用操作符重載和調(diào)用轉(zhuǎn)換操作符等等都沒(méi)有辦法返回錯(cuò)誤代碼,但是在這些構(gòu)造中又需要報(bào)告錯(cuò)誤,那就必須提供異常處理機(jī)制。

在異常的處理中,我們經(jīng)常使用到的三個(gè)塊分別是:try塊;catch塊;finally塊。這三個(gè)塊可以一起使用,也可以不寫catch塊使用,異常處理塊可以嵌套使用,具體的方法在下面會(huì)介紹到。

在異常的處理機(jī)制中,一般有三種選擇:重新拋出相同的異常,向調(diào)用棧高一層的代碼通知該異常的發(fā)生;拋出一個(gè)不同的異常,想調(diào)用棧高一層代碼提供更豐富的異常信息;讓線程從catch塊的底部退出。  

有關(guān)異常的處理方式,有一些指導(dǎo)性的建議。

1.恰當(dāng)?shù)氖褂胒inally塊:

finally塊可以保證不管線程拋出什么類型的異常都可以被執(zhí)行,finall塊一般用來(lái)做清理那些已經(jīng)成功啟動(dòng)的操作,然后再返回調(diào)用者或者finally塊之后的代碼。

2.異常捕捉需適當(dāng):

為什么要適當(dāng)?shù)牟蹲疆惓D??如下代碼,因?yàn)槲覀儾荒苁裁串惓6既ゲ蹲?,在捕獲異常后,我們需要去處理這些異常,如果我們將所有的異常都捕捉后,但是沒(méi)有預(yù)見(jiàn)會(huì)發(fā)生的異常,我們就沒(méi)有辦法去處理這些異常。

如果應(yīng)用程序代碼拋出一個(gè)異常,應(yīng)用程序的另一端則可能預(yù)期要捕捉這個(gè)異常,因此不能寫成一個(gè)”大小通吃“的異常塊,應(yīng)該允許該異常在調(diào)用棧中向上移動(dòng),讓應(yīng)用程序代碼針對(duì)性地處理這個(gè)異常。

在catch塊中,可以使用System.Exception捕捉異常,但是最好在catch塊末尾重新拋出異常。至于原因在后面會(huì)講解到。

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("創(chuàng)建注冊(cè)表錯(cuò)誤" + ex);
  throw new Exception(ex.Message,ex);
  }

3.從異常中恢復(fù):

我們?cè)诓东@異常后,可以針對(duì)性的寫一些異常恢復(fù)的代碼,可以讓程序繼續(xù)運(yùn)行。在捕獲異常時(shí),需要捕獲具體的異常,充分的掌握在什么情況下會(huì)拋出異常,并知道從捕獲的異常類型派生出了那些類型。除非在catch塊的末尾重新拋出異常,否則不要處理或捕獲System.Exception異常。

4.維持狀態(tài):

一般情況下,我們完成一個(gè)操作或者一個(gè)方法時(shí),需要調(diào)用幾個(gè)方法組合完成,在執(zhí)行的過(guò)程中會(huì)出現(xiàn)前面幾個(gè)方法完成,后面的方法發(fā)生異常。發(fā)生不可恢復(fù)的異常時(shí)回滾部分完成的操作,因?yàn)槲覀冃枰謴?fù)信息,所有我們?cè)诓东@異常時(shí),需要捕獲所有的異常信息。

5.隱藏實(shí)現(xiàn)細(xì)節(jié)來(lái)維持契約:

有時(shí)可能需要捕捉一個(gè)異常并重新拋出一個(gè)不同的異常,這樣可以維系方法的契約,拋出的心異常類型地應(yīng)該是一個(gè)具體的異常??慈缦麓a:

FileStream fs = null;
  try
  {
  fs = FileStream();
  
  }
  catch (FileNotFoundException e)
  {
          //拋出一個(gè)不同的異常,將異常信息包含在其中,并將原來(lái)的異常設(shè)置為內(nèi)部異常
  throw new NameNotFoundException();
  }
  catch (IOException e)
  {
 
  //拋出一個(gè)不同的異常,將異常信息包含在其中,并將原來(lái)的異常設(shè)置為內(nèi)部異常
  throw new NameNotFoundException(); 
  } 
  finally 
  {
  if (fs != null) 
  { 
  fs.close(); 
  } 
  }

以上的代碼只是在說(shuō)明一種處理方式。應(yīng)該讓拋出的所有異常都沿著方法的調(diào)用棧向上傳遞,而不是把他們”吞噬“了之后拋出一個(gè)新的異常。如果一個(gè)類型構(gòu)造器拋出一個(gè)異常,而且該異常未在類型構(gòu)造器方法中捕獲,CLR就會(huì)在內(nèi)部捕獲該異常,并改為拋出一個(gè)新的TypeInitialztionException。

二.DotNET異常的常用處理機(jī)制:

在代碼發(fā)生異常后,我們需要去處理這個(gè)異常,如果一個(gè)異常沒(méi)有得到及時(shí)的處理,CLR會(huì)終止進(jìn)程。在異常的處理中,我們可以在一個(gè)線程捕獲異常,在另一個(gè)線程中重新拋出異常。異常拋出時(shí),CLR會(huì)在調(diào)用棧中向上查找與拋出的異常類型匹配的catch塊。如果沒(méi)有任何catch塊匹配拋出的異常類型,就發(fā)生一個(gè)未處理異常。CLR檢測(cè)到進(jìn)程中的任何線程有一個(gè)位處理異常,都會(huì)終止進(jìn)程。

1.異常處理塊:

(1).try塊:包含代碼通常需要執(zhí)行一些通用的資源清理操作,或者需要從異常中恢復(fù),或者兩者都需要。try塊還可以包含也許會(huì)拋出異常的代碼。一個(gè)try塊至少有一個(gè)關(guān)聯(lián)的catch塊或finall塊。      

(2).catch塊:包含的是響應(yīng)一個(gè)異常需要執(zhí)行的代碼。catch關(guān)鍵字后的圓括號(hào)中的表達(dá)式是捕獲類型。捕獲類型從System.Exception或者其派生類指定。CLR自上而下搜素一個(gè)匹配的catch塊,所以應(yīng)該教具體的異常放在頂部。一旦CLR找到一個(gè)具有匹配捕獲類型的catch塊,就會(huì)執(zhí)行內(nèi)層所有finally塊中的代碼,”內(nèi)層finally“是指拋出異常的tey塊開(kāi)始,到匹配異常的catch塊之間的所有finally塊。

使用System.Exception捕捉異常后,可以采用在catch塊的末尾重新拋出異常,因?yàn)槿绻覀冊(cè)诓东@Exception異常后,沒(méi)有及時(shí)的處理或者終止程序,這一異??赡軐?duì)程序造成很大的安全隱患,Exception類是所有異常的基類,可以捕獲程序中所有的異常,如果出現(xiàn)較大的異常,我們沒(méi)有及時(shí)的處理,造成的問(wèn)題是巨大的。

(3).finally塊:包含的代碼是保證會(huì)執(zhí)行的代碼。finally塊的所有代碼執(zhí)行完畢后,線程退出finally塊,執(zhí)行緊跟在finally塊之后的語(yǔ)句。如果不存在finally塊,線程將從最后一個(gè)catch塊之后的語(yǔ)句開(kāi)始執(zhí)行。

備注:異常塊可以組合和嵌套,對(duì)于三個(gè)異常塊的樣例,在這里就不做介紹,異常的嵌套可以防止在處理異常的時(shí)候再次出現(xiàn)未處理的異常,以上這些就不再贅述。

2.異常處理實(shí)例:

(1).異常處理擴(kuò)展方法:

 /// <summary>
 /// 格式化異常消息
 /// </summary>
 /// <param name="e">異常對(duì)象</param>
 /// <param name="isHideStackTrace">是否隱藏異常規(guī)模信息</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}內(nèi)部異常:", appString));
   count++;
  }
  e = e.InnerException;
  }
  return sb.ToString();
 }

(2).驗(yàn)證異常:

 /// <summary>
 /// 檢查字符串是空的或空的,并拋出一個(gè)異常
 /// </summary>
 /// <param name="val">值測(cè)試</param>
 /// <param name="paramName">參數(shù)檢查名稱</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>
 /// 請(qǐng)檢查參數(shù)不是空的或空的,并拋出異常
 /// </summary>
 /// <param name="param">檢查值</param>
 /// <param name="paramName">參數(shù)名稱</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>
 /// 檢查參數(shù)不是無(wú)效,并拋出一個(gè)異常
 /// </summary>
 /// <param name="param">檢查值</param>
 /// <param name="paramName">參數(shù)名稱</param>
 public static void CheckNullParam(object param, string paramName)
 {
  if (param == null)
  throw new ArgumentNullException(paramName, paramName + " can't be null");
 }
 /// <summary>
 /// 請(qǐng)檢查參數(shù)1不同于參數(shù)2
 /// </summary>
 /// <param name="param1">值1測(cè)試</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>
 /// 檢查一個(gè)整數(shù)值是正的(0或更大)
 /// </summary>
 /// <param name="val">整數(shù)測(cè)試</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擴(kuò)展操作:

/// <summary>
 /// 對(duì)某對(duì)象執(zhí)行指定功能與后續(xù)功能,并處理異常情況
 /// </summary>
 /// <typeparam name="T">對(duì)象類型</typeparam>
 /// <param name="source">值</param>
 /// <param name="action">要對(duì)值執(zhí)行的主功能代碼</param>
 /// <param name="failureAction">catch中的功能代碼</param>
 /// <param name="successAction">主功能代碼成功后執(zhí)行的功能代碼</param>
 /// <returns>主功能代碼是否順利執(zhí)行</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>
 /// 對(duì)某對(duì)象執(zhí)行指定功能,并處理異常情況
 /// </summary>
 /// <typeparam name="T">對(duì)象類型</typeparam>
 /// <param name="source">值</param>
 /// <param name="action">要對(duì)值執(zhí)行的主功能代碼</param>
 /// <param name="failureAction">catch中的功能代碼</param>
 /// <returns>主功能代碼是否順利執(zhí)行</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>
 /// 對(duì)某對(duì)象執(zhí)行指定功能,并處理異常情況與返回值
 /// </summary>
 /// <typeparam name="T">對(duì)象類型</typeparam>
 /// <typeparam name="TResult">返回值類型</typeparam>
 /// <param name="source">值</param>
 /// <param name="func">要對(duì)值執(zhí)行的主功能代碼</param>
 /// <param name="failureAction">catch中的功能代碼</param>
 /// <param name="successAction">主功能代碼成功后執(zhí)行的功能代碼</param>
 /// <returns>功能代碼的返回值,如果出現(xiàn)異常,則返回對(duì)象類型的默認(rèn)值</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>
 /// 對(duì)某對(duì)象執(zhí)行指定功能,并處理異常情況與返回值
 /// </summary>
 /// <typeparam name="T">對(duì)象類型</typeparam>
 /// <typeparam name="TResult">返回值類型</typeparam>
 /// <param name="source">值</param>
 /// <param name="func">要對(duì)值執(zhí)行的主功能代碼</param>
 /// <param name="failureAction">catch中的功能代碼</param>
 /// <returns>功能代碼的返回值,如果出現(xiàn)異常,則返回對(duì)象類型的默認(rèn)值</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 => { });
 }

本文沒(méi)有具體介紹try,catch,finally的使用,而是給出一些比較通用的方法,主要是一般的開(kāi)發(fā)者對(duì)于三個(gè)塊的使用都有一個(gè)認(rèn)識(shí),就不再做重復(fù)的介紹。

三.DotNET的Exception類分析:

CLR允許異常拋出任何類型的實(shí)例,這里我們介紹一個(gè)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只具有g(shù)et屬性,所以message是只讀屬性。GetClassName()獲取異常的類。GetRuntimeResourceString()獲取運(yùn)行時(shí)資源字符串。

2.StackTrace屬性:包含拋出異常之前調(diào)用過(guò)的所有方法的名稱和簽名。

 public static string StackTrace
{
 [SecuritySafeCritical]
 get
 {
 new EnvironmentPermission(PermissionState.Unrestricted).Demand();
 return GetStackTrace(null, true);
 }
}

EnvironmentPermission()用于環(huán)境限制,PermissionState.Unrestricted設(shè)置權(quán)限狀態(tài),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);
}

 以上是獲取堆棧跟蹤方法的具體實(shí)現(xiàn),此方法主要用戶調(diào)試的時(shí)候。

3.GetBaseException()獲取基礎(chǔ)異常信息方法。

 [__DynamicallyInvokable]
public virtual Exception GetBaseException()
{
 Exception innerException = this.InnerException;
 Exception exception2 = this;
 while (innerException != null)
 {
 exception2 = innerException;
 innerException = innerException.InnerException;
 }
 return exception2;
}

InnerException屬性是內(nèi)在異常,這是一個(gè)虛方法,在這里被重寫。具體看一下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;
}

在此方法中,將獲取的異常信息進(jìn)行格式化為字符串,this.GetClassName() 獲取異常類的相關(guān)信息。

以上我們注意到[__DynamicallyInvokable]定制屬性,我們看一下具體的實(shí)現(xiàn)代碼:

[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
public __DynamicallyInvokableAttribute()
{
}

以上我們主要注釋部分,”圖像邊界“這個(gè)屬性的相關(guān)信息,請(qǐng)參見(jiàn)《CLR via C#》,這里就不做具體的介紹。

以上在對(duì)異常的介紹中,主要介紹了CLR的異常處理機(jī)制,一些較為通用的異常代碼,以及對(duì)Exception類的介紹。在實(shí)際的項(xiàng)目中,我們一般不要將異常直接拋出給客戶,我們?cè)诰帉懗绦驎r(shí),已經(jīng)考慮程序的容錯(cuò)性,在程序捕獲到異常后,盡量去恢復(fù)程序,或者將異常信息寫入日志,讓程序進(jìn)入錯(cuò)誤頁(yè)。如果出現(xiàn)比較嚴(yán)重的異常,最后將異常拋出,終止程序。

上述內(nèi)容就是如何解決.NET的異常處理,你們學(xué)到知識(shí)或技能了嗎?如果還想學(xué)到更多技能或者豐富自己的知識(shí)儲(chǔ)備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI