SoFunction
Updated on 2025-03-08

Detailed explanation of exception creation, raising and exception handling in C# programming

Create and raise exceptions
An exception is used to indicate that an error occurred while running the program. An exception object describing the error is created and then "raised" the object using the throw keyword. Then runtime search for the most compatible exception handler.
The programmer should raise an exception when one or more of the following situations exist:
The method cannot complete the functions defined in it.
For example, if the parameter of the method has an invalid value:

static void CopyObject(SampleClass original)
{
  if (original == null)
  {
    throw new ("Parameter cannot be null", "original");
  }

}

Depending on the state of the object, an inappropriate call is made to an object.
An example might attempt to write to a read-only file. In the case where an operation is not allowed in the object state, an instance of InvalidOperationException or an object based on a derived class of such a class is raised. The following is an example of a method that raises an InvalidOperationException object:

class ProgramLog
{
   logFile = null;
  void OpenLog( fileName,  mode) {}

  void WriteLog()
  {
    if (!)
    {
      throw new ("Logfile cannot be read-only");
    }
    // Else write data to the log and return.
  }
}

The parameters of the method result in an exception.
In this case, the original exception should be caught and an ArgumentException instance should be created. The original exception should be passed as an InnerException parameter to the constructor of ArgumentException:

static int GetValueFromArray(int[] array, int index)
{
  try
  {
    return array[index];
  }
  catch ( ex)
  {
     argEx = new ("Index is out of range", "index", ex);
    throw argEx;
  }
}

The exception contains a property called StackTrace. This string contains the name of the method on the current call stack, and the location (file name and line number) that raised the exception for each method. StackTrace objects are automatically created by the Common Language Runtime (CLR) from the throw statement point, so an exception must be thrown from the start point of the stack trace.
All exceptions contain a property called Message. This string should be set to explain the cause of the exception. Note that security-sensitive information should not be placed in the message text. In addition to Message, ArgumentException also contains a property named ParamName, which should be set to the name of the parameter that caused the exception to be thrown. For property setters, ParamName should be set to value.
A public protected method should raise an exception if it fails to perform the expected function. The exception class raised should be the most exact available exception that meets the error condition. These exceptions should be written as part of the class function, and the derivative class or update to the original class should retain the same behavior for backward compatibility.
Things to avoid when throwing an exception
The following table determines what to avoid when an exception is thrown:

  • Exceptions should not be used to change the program flow during normal execution. Exceptions can only be used to report and handle error conditions.
  • Only exceptions can be raised, but cannot be returned as return values ​​or parameters.
  • Do not intentionally raise , , or , from your own source code.
  • Do not create exceptions that can be raised in debug mode but not in release mode. To determine runtime errors during the development phase, use debug assertions instead.

Define exception class

Programs can raise predefined exception classes in the System namespace (except as previously noted), or create their own exception classes by deriving from Exception. A derived class should define at least four constructors: one is the default constructor, one is used to set the message attribute, and one is used to set the Message attribute and the InnerException attribute. The fourth constructor is used to serialize exceptions. The new exception class should be serializable. For example:

public class InvalidDepartmentException : 
{
  public InvalidDepartmentException() : base() { }
  public InvalidDepartmentException(string message) : base(message) { }
  public InvalidDepartmentException(string message,  inner) : base(message, inner) { }

  // A constructor is needed for serialization when an
  // exception propagates from a remoting server to the client. 
  protected InvalidDepartmentException( info,
     context) { }
}

Add the data provided by the new attribute to the exception class only if it helps resolve the exception. If a new property is added to the derived exception class, ToString() should be overridden to return the added information.


Exception handling
C# programmers can use try blocks to partition code that may be affected by exceptions. The associated catch block is used to handle any result exceptions. A finally block containing code that runs regardless of whether an exception is raised in the try block (for example, freeing resources allocated in the try block). A try block requires one or more associated catch blocks or a finally block, or both.
The following example gives a try-catch statement, a try-finally statement, and a try-catch-finally statement.

 try
{
  // Code to try goes here.
}
catch (SomeSpecificException ex)
{
  // Code to handle the exception goes here.
  // Only catch exceptions that you know how to handle.
  // Never catch base class  without
  // rethrowing it at the end of the catch block.
}

 try
{
  // Code to try goes here.
}
finally
{
  // Code to execute after the try block goes here.
}

 try
{
  // Code to try goes here.
}
catch (SomeSpecificException ex)
{
  // Code to handle the exception goes here.
}
finally
{
  // Code to execute after the try (and possibly catch) blocks 
  // goes here.
}

A try block without catch or finally block will cause a compiler error.
Catch block
The catch block can specify the type of exception to be caught. The type specification is called "Exception Filter". The exception type should be derived from Exception. Generally, Exception is not specified as an exception filter unless you understand how to handle all exceptions that may be raised in a try block, or you include a throw statement in the catch block.
Multiple catch blocks with different exception filters can be concatenated together. The order in which multiple catch blocks are calculated is from top to bottom in the code, but only one catch block is executed for each exception raised. The first catch block that best matches the specified exact type or its base class is executed. If the catch block does not specify a matching exception filter, the catch block does not have the selected filter (if the statement has it). A catch block with the most specific (i.e. the most derived) exception class needs to be placed first.
Exceptions should be caught when the following conditions are true:
Have a specific understanding of the causes of the exception and can implement specific recovery, for example, prompting the user to enter a new file name when capturing the FileNotFoundException object.
A new, more specific exception can be created and raised.

int GetInt(int[] array, int index)
{
  try
  {
    return array[index];
  }
  catch( e)
  {
    throw new (
      "Parameter index is out of range.");
  }
}

It is desirable to partially handle the exception before passing it out for additional processing. In the following example, the catch block is used to add an entry to the error log before the exception is raised again.

try
{
  // Try to access a resource.
}
catch ( e)
{
  // Call a custom error logging procedure.
  LogError(e);
  // Re-throw the error.
  throw;   
}

Finally block
You can use the finally block to clean up operations performed in the try block. If present, the finally block will be executed at the end, after the try block and any block matching catch. Even if the exception is raised or if a catch block matching the exception type is found, finally always runs.
You can use finally blocks to free resources (such as file streams, database connections, and graphics handles) without waiting for objects to be completed by the garbage collector in the runtime.

In the following example, use the finally block to close a file opened in the try block. Note that the file handle status must be checked before closing it. If the try block fails to open the file, the file handle still has the value null, and the finally block does not try to close it. Alternatively, if the file is successfully opened in the try block, the finally block will close the open file.

 file = null;
 fileinfo = new ("C:\\");
try
{
  file = ();
  (0xF);
}
finally
{
  // Check for null because OpenWrite might have failed.
  if (file != null)
  {
    ();
  }
}