Summary of thoughts on. NET exception handling

  • 2021-09-04 23:55:28
  • OfStack

As the year is approaching, most programmers can be idle for a period of time soon. However, in this leisure time, only arguing which language is better can kill time. It is estimated that there will be many blog posts about java and. net recently. I said that as a melon eater, I would quietly watch the bosses express their feelings.

There's enough nonsense above, so let's stop talking nonsense here. Let's get down to business.

In the project development, there are corresponding requirements for the stability and fault tolerance of the system and code. The difference between the code in the actual development project and the sample code is more in the comparison of the stability, fault tolerance and extensibility of the code. Because for the realization of a function, the core code for the realization of the function is 1, which may only be optimized in writing, but for the class used in the realization of a certain operation, this 1 point is 1 most of the time. In this way, in the actual development process, we need to consider many problems, which are not only limited to the realization of a specific function, but also the stability and extensibility of the code.

These are the problems that need to be faced in actual development. In the recent blog post, the author is also considering how to write this anomaly and how to understand it. I hope it will help you. I also welcome you to put forward your own ideas and opinions and share your knowledge and opinions.

1. Overview of DotNET exception:

When it comes to anomaly, we need to know what anomaly is. If we want to learn everything, we should know what we want to learn, so that we can have a general cognition in our hearts. An exception means that a member fails to complete an action that its name claims can be completed. In. NET, constructors, getting and setting properties, adding and deleting events, calling operator overloads, calling conversion operators, etc. have no way to return error codes, but in these constructs, errors need to be reported, so exception handling mechanisms must be provided.

In exception handling, we often use three blocks: try block; catch block; finally block. These three blocks can be used from one to another, or they can be used without writing catch blocks, and exception handling blocks can be nested. The specific methods will be introduced below.

In the exception handling mechanism, 1 generally has three choices: throw the same exception again and notify the code one level higher in the call stack of the occurrence of the exception; Throw a different exception, and want to call the code one layer higher than the stack to provide richer exception information; Exit the thread from the bottom of the catch block.

There are some guiding suggestions on how to handle exceptions.

1. Appropriate use of finally block:

The finally block ensures that any type of exception thrown by the thread can be executed, and the finall block 1 is generally used to clean up those operations that have been successfully started, and then return to the caller or the code after the finally block.

2. Exception catching should be appropriate:

Why catch exceptions properly? The following code, because we can't catch all exceptions, after catching exceptions, we need to handle these exceptions. If we catch all exceptions but don't foresee the exceptions that will happen, we can't handle these exceptions.

If the application code throws an exception, the other side of the application may expect to catch the exception, so it cannot be written as a "size-take-all" exception block, and should allow the exception to move up the call stack so that the application code handles the exception in a targeted manner.

In the catch block, you can use System. Exception to catch exceptions, but it is best to re-throw exceptions at the end of the catch block. The reasons will be explained later.


   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(" Error in creating registry " + ex);
    throw new Exception(ex.Message,ex);
   }

3. Recovering from an exception:

After we catch the exception, we can write some exception recovery code, and let the program continue to run. When catching exceptions, you need to catch specific exceptions, fully understand when exceptions are thrown, and know which types are derived from the caught exception types. Do not handle or catch System. Exception exceptions unless the exception is re-thrown at the end of the catch block.

4. Maintain status:

Under normal circumstances, when we complete an operation or a method, we need to call several methods to complete. In the process of execution, the previous methods will be completed, and the latter methods will be excepted. Roll back the partially completed operation when an unrecoverable exception occurs, because we need to recover the information, so when we catch the exception, we need to catch all the exception information.

5. Hide the implementation details to maintain the contract:

Sometimes it may be necessary to catch an exception and throw a different exception again, so that the contract of the method can be maintained, and the thrown exception type should be a specific exception. Look at the following code:


FileStream fs = null;
   try
   {
    fs = FileStream();
    
   }
   catch (FileNotFoundException e)
   {
            // Throw 1 Different exceptions, including exception information and setting the original exception as an internal exception 
    throw new NameNotFoundException();
   }
   catch (IOException e)
   {
 
    // Throw 1 Different exceptions, including exception information and setting the original exception as an internal exception 
    throw new NameNotFoundException(); 
   } 
   finally 
   {
    if (fs != null) 
    { 
    fs.close(); 
   } 
   }

The above code is only illustrating one processing method. You should let all exceptions thrown pass up the method's call stack, instead of throwing a new exception after "devouring" them. If a type constructor throws an exception and the exception is not caught in the type constructor method, CLR catches the exception internally and throws a new TypeInitialztionException instead.

2. Common handling mechanisms for DotNET exceptions:

After an exception occurs in the code, we need to handle the exception. If an exception is not handled in time, CLR will terminate the process. In exception handling, we can catch an exception in one thread and throw it again in another thread. When an exception is thrown, CLR looks up the call stack for an catch block that matches the type of exception being thrown. If no catch blocks match the type of exception thrown, an unhandled exception occurs. CLR detects that any thread in the process has a 1-bit handling exception and terminates the process.

1. Exception Handling Block:

(1). try Block: Include code that typically needs to perform some common resource cleanup operations, or recover from exceptions, or both. The try block can also contain code that might throw an exception. An try block has at least one associated catch block or finall block.

(2). catch Block: Contains the code that needs to be executed in response to an exception. The expression in parentheses after the catch keyword is a capture type. The capture type is specified from System. Exception or its derived classes. CLR searches for a matching catch block from top to bottom, so specific exceptions should be taught at the top. 1 Once CLR finds an catch block with a matching capture type, it executes the code in all finally blocks in the inner layer. "Inner finally" refers to all finally blocks from the tey block that threw the exception to the catch block that matched the exception.

After catching an exception using System. Exception, You can re-throw the exception at the end of the catch block, Because if we catch Exception exception, not timely processing or terminating the program, this 1 exception may cause great security risks to the program. Exception class is the base class of all exceptions, which can capture all exceptions in the program. If there is a larger exception, we do not deal with it in time, resulting in huge problems.

(3). finally Block: Contains code that is guaranteed to be executed. After all the code for the finally block has been executed, the thread exits the finally block and executes the statement immediately following the finally block. If no finally block exists, the thread executes from the statement following the last catch block.

Note: Exception blocks can be combined and nested. For the sample of 3 exception blocks, we will not introduce them here. The nesting of exceptions can prevent unhandled exceptions from appearing again when handling exceptions, so we will not repeat them here.

2. Exception handling instance:

(1). Exception handling extension method:


  /// <summary>
  ///  Formatting exception messages 
  /// </summary>
  /// <param name="e"> Exception object </param>
  /// <param name="isHideStackTrace"> Whether to hide exception size information </param>
  /// <returns> Formatted exception information string </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} Exception message: {1}", appString, e.Message));
    sb.AppendLine(string.Format("{0} Exception type: {1}", appString, e.GetType().FullName));
    sb.AppendLine(string.Format("{0} Exception method: {1}", appString, (e.TargetSite == null ? null : e.TargetSite.Name)));
    sb.AppendLine(string.Format("{0} Exception source: {1}", appString, e.Source));
    if (!isHideStackTrace && e.StackTrace != null)
    {
     sb.AppendLine(string.Format("{0} Exception stack: {1}", appString, e.StackTrace));
    }
    if (e.InnerException != null)
    {
     sb.AppendLine(string.Format("{0} Internal exception: ", appString));
     count++;
    }
    e = e.InnerException;
   }
   return sb.ToString();
  }

(2). Validate exceptions:


  /// <summary>
  ///  Checks whether the string is empty or empty, and throws 1 Anomalies 
  /// </summary>
  /// <param name="val"> Value test </param>
  /// <param name="paramName"> Parameter check name </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>
  ///  Please check that the parameter is not empty or empty and throw an exception 
  /// </summary>
  /// <param name="param"> Check value </param>
  /// <param name="paramName"> Parameter name </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>
  ///  Checks that the parameter is not invalid and throws 1 Anomalies 
  /// </summary>
  /// <param name="param"> Check value </param>
  /// <param name="paramName"> Parameter name </param>
  public static void CheckNullParam(object param, string paramName)
  {
   if (param == null)
    throw new ArgumentNullException(paramName, paramName + " can't be null");
  }

  /// <summary>
  ///  Please check the parameters 1 Different from parameters 2
  /// </summary>
  /// <param name="param1"> Value 1 Test </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>
  ///  Check 1 Integer values are positive ( 0 Or larger) 
  /// </summary>
  /// <param name="val"> Integer test </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 extension operations:


  /// <summary>
  ///   Performs specified and subsequent functions on an object and handles exceptions 
  /// </summary>
  /// <typeparam name="T"> Object type </typeparam>
  /// <param name="source"> Value </param>
  /// <param name="action"> The main function code to execute on the value </param>
  /// <param name="failureAction">catch Function codes in </param>
  /// <param name="successAction"> Function code executed after the main function code is successful </param>
  /// <returns> Is the main function code executed smoothly </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>
  ///   Performs a specified function on an object and handles exceptions 
  /// </summary>
  /// <typeparam name="T"> Object type </typeparam>
  /// <param name="source"> Value </param>
  /// <param name="action"> The main function code to execute on the value </param>
  /// <param name="failureAction">catch Function codes in </param>
  /// <returns> Is the main function code executed smoothly </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>
  ///   Performs specified functions on an object and handles exceptions and return values 
  /// </summary>
  /// <typeparam name="T"> Object type </typeparam>
  /// <typeparam name="TResult"> Return value type </typeparam>
  /// <param name="source"> Value </param>
  /// <param name="func"> The main function code to execute on the value </param>
  /// <param name="failureAction">catch Function codes in </param>
  /// <param name="successAction"> Function code executed after the main function code is successful </param>
  /// <returns> The return value of the function code, and if an exception occurs, the default value of the object type </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>
  ///   Performs specified functions on an object and handles exceptions and return values 
  /// </summary>
  /// <typeparam name="T"> Object type </typeparam>
  /// <typeparam name="TResult"> Return value type </typeparam>
  /// <param name="source"> Value </param>
  /// <param name="func"> The main function code to execute on the value </param>
  /// <param name="failureAction">catch Function codes in </param>
  /// <returns> The return value of the function code, and if an exception occurs, the default value of the object type </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 => { });
  }

This article does not specifically introduce the use of try, catch and finally, but gives a more general method, mainly because 1 general developer has a knowledge of the use of 3 blocks, so he will not repeat the introduction.

3. Exception class analysis of DotNET:

CLR allows exceptions to throw instances of any type, and here we introduce an System. Exception class:

1. Message property: Indicates the reason why the exception was thrown.


[__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 });
 }
}

As you can see from the above code, Message only has the get attribute, so message is a read-only attribute. GetClassName () gets the class of the exception. GetRuntimeResourceString () gets the runtime resource string.

2. StackTrace property: Contains the names and signatures of all methods that were called before the exception was thrown.


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

EnvironmentPermission () is used for environmental restrictions, PermissionState. Unrestricted sets the permission status, GetStackTrace () obtains the stack trace, and specifically looks at the code of GetStackTrace () under 1.


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);
}

The above is the specific implementation of the method of obtaining stack trace, which is mainly used when debugging.

3. GetBaseException () method to get basic exception information.


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

The InnerException property is an intrinsic exception, which is a virtual method and is overridden here. See the InnerException attribute under 1 specifically.


FileStream fs = null;
   try
   {
    fs = FileStream();
    
   }
   catch (FileNotFoundException e)
   {
            // Throw 1 Different exceptions, including exception information and setting the original exception as an internal exception 
    throw new NameNotFoundException();
   }
   catch (IOException e)
   {
 
    // Throw 1 Different exceptions, including exception information and setting the original exception as an internal exception 
    throw new NameNotFoundException(); 
   } 
   finally 
   {
    if (fs != null) 
    { 
    fs.close(); 
   } 
   }

0

4. ToString () formats exception information.


FileStream fs = null;
   try
   {
    fs = FileStream();
    
   }
   catch (FileNotFoundException e)
   {
            // Throw 1 Different exceptions, including exception information and setting the original exception as an internal exception 
    throw new NameNotFoundException();
   }
   catch (IOException e)
   {
 
    // Throw 1 Different exceptions, including exception information and setting the original exception as an internal exception 
    throw new NameNotFoundException(); 
   } 
   finally 
   {
    if (fs != null) 
    { 
    fs.close(); 
   } 
   }

1

In this method, the retrieved exception information is formatted as a string, and this. GetClassName () retrieves information about the exception class.

Above, we noticed the [__DynamicallyInvokable] custom attribute. Let's look at the specific implementation code under 1:


FileStream fs = null;
   try
   {
    fs = FileStream();
    
   }
   catch (FileNotFoundException e)
   {
            // Throw 1 Different exceptions, including exception information and setting the original exception as an internal exception 
    throw new NameNotFoundException();
   }
   catch (IOException e)
   {
 
    // Throw 1 Different exceptions, including exception information and setting the original exception as an internal exception 
    throw new NameNotFoundException(); 
   } 
   finally 
   {
    if (fs != null) 
    { 
    fs.close(); 
   } 
   }

2

Please refer to "Via CLR c #" for the related information of the attribute "Image Boundary" in our main comment section above, which will not be introduced in detail here.

4. Summary:

In the above introduction to exceptions, we mainly introduce the exception handling mechanism of CLR, some more general exception codes, and the introduction of Exception classes. In the actual project, we should not throw the exception directly to the customer. When we write the program, we have considered the fault tolerance of the program. After the program catches the exception, we try our best to restore the program, or write the exception information into the log to let the program enter the error page. If there is a serious exception, the exception will be thrown and the program will be terminated.


Related articles: