.NET例外処理を分析する

巴扎黑
巴扎黑オリジナル
2017-08-16 17:06:191647ブラウズ

この記事は主に .NET の例外処理に関する考え方をまとめたものです。興味のある方は参考にしてください。

新年が近づいており、ほとんどのプログラマーにとって、この自由な時間で時間を潰す唯一の方法は、どの言語が優れているかを議論することであると推定されています。最近 Java についてよく話題になるでしょう。.net のブログ記事が掲載されたとき、私はメロンを食べる者として、偉い人たちが感情を表現するのを静かに見守るつもりだと言いました。

ナンセンスなことは上で十分述べたので、ここではナンセンスには立ち入らず、本題に入りましょう。

プロジェクト開発では、システムとコードの安定性とフォールトトレランスに対応する要件があります。実際の開発プロジェクトのコードとサンプル コードの違いは、コードの動作の安定性、フォールト トレランス、スケーラビリティにあります。関数を実装する場合、その関数を実装するためのコア コードは同じであり、記述に関して最適化されているだけである可能性がありますが、特定の操作の実装に使用されるクラスに関しては、ほとんどの場合同じです。この観点から、実際の開発プロセスでは、特定の機能の実装に限定されず、コードの安定性や拡張性など、多くの点を考慮する必要があります。

上記は実際の開発で直面する必要がある問題であり、著者は最近のブログ投稿でこの例外の書き方と例外の理解方法についても検討しています。誰でも自分のアイデアや意見を考え出し、知識や洞察を共有してください。

1. DotNET 例外の概要:

例外についてすべてを学びたい場合は、何を学びたいのかを知る必要があります。大まかな理解ができて良かったです。メンバーが、その名前に示されている実行可能なアクションの実行に失敗すると、例外が発生します。 .NET では、コンストラクター、プロパティの取得と設定、イベントの追加と削除、演算子のオーバーロードの呼び出し、変換演算子の呼び出しなどでは、エラー コードを返す方法がありません。ただし、これらのコンストラクターでエラーを報告する必要がある場合は、例外を返す必要があります。処理メカニズムが提供されます。

例外処理では、try ブロック、finally ブロックの 3 つのブロックがよく使用されます。これら 3 つのブロックは併用することも、catch ブロックを使用せずにネストして使用することもできます。その具体的な方法を以下に紹介します。

例外処理メカニズムには、通常 3 つのオプションがあります。同じ例外を再スローし、例外が発生したことをコール スタック内の上位レベルのコードに通知し、別の例外を上位レベルのコードに提供します。呼び出しスタックの豊富な例外情報により、スレッドが catch ブロックの最後から抜け出すようになります。

例外を処理する方法については、いくつかの指針となる提案があります。

1.finally ブロックの適切な使用:

final ブロッ​​クは、通常、スレッドがスローした例外の種類に関係なく、正常に開始された操作をクリーンアップするために使用されます。 、または、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&#39;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&#39;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&#39;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&#39;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类的介绍。在实际的项目中,我们一般不要将异常直接抛出给客户,我们在编写程序时,已经考虑程序的容错性,在程序捕获到异常后,尽量去恢复程序,或者将异常信息写入日志,让程序进入错误页。如果出现比较严重的异常,最后将异常抛出,终止程序。

以上が.NET例外処理を分析するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。