This is the online documentation for PostSharp 5.0.
Download PDF or CHM. Go to v4.3 or v5.0

Handling Exceptions

Adding exception handlers to code requires the addition of try/catch statements which can quickly pollute code. Exception handling implemented this way is also not reusable, requiring the same logic to be implemented over and over where ever exceptions must be dealt with. Raw exceptions also present cryptic information and can often expose too much information to the user.

PostSharp provides a solution to these problems by allowing custom exception handling logic to be encapsulated into a reusable class, which is then easily applied as an attribute to all methods and properties where exceptions are to be dealt with.

This topic contains the following sections:

  • Intercepting an exception
  • Specifying the type of handled exceptions
  • Ignoring exceptions
  • Replacing exceptions
  • Displaying the method arguments on exception
Intercepting an exception

PostSharp provides the OnExceptionAspect class which is the base class from which exception handlers are to be derived from.

The key element of this class is the OnException(MethodExecutionArgs) method: this is the method where the exception handling logic (i.e. what would normally be in a catch statement) goes. A MethodExecutionArgs parameter is passed into this method by PostSharp; it contains information about the exception.

To create an OnExceptionAspect class:

  1. Derive a class from OnExceptionAspect.

  2. Apply the PSerializableAttribute to the class.

  3. Override OnException(MethodExecutionArgs) and implement your exception handling logic in this class.

The following snippet shows an example of an exception handler which watches for exceptions of any type, and then writes a message to the console when an exception occurs:

C#
[PSerializable]
public class PrintExceptionAttribute : OnExceptionAspect
{

    public override void OnException(MethodExecutionArgs args)
    {
        Console.WriteLine(args.Exception.Message);
    }
}

Once created, apply the derived class to all methods and/or properties for which the exception handling logic is to be used, as shown in the following example:

C#
class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    [PrintException]
    public void StoreName(string path)
    {
        File.WriteAllText( path, string.Format( “{0} {1}”, this.FirstName, this.LastName ) );
    }

}

Here PrintException will output a message when an exception occurs in trying to write text to a file.

Alternatively the attribute can be applied to the class itself as shown below, in which case the exception handler will handle exceptions for all methods and properties in the class:

C#
[PrintExceptionAttribute(typeof(IOException))]
    class Customer
    {
      .
      .
      .
    }

See the section Adding Aspects to Multiple Declarations for details about attribute multicasting.

Specifying the type of handled exceptions

The GetExceptionType(MethodBase) method can be used to return the type of the exception which is to be handled by this aspect. Otherwise, all exceptions will be caught and handled by this class.

Note Note

The GetExceptionType(MethodBase) method is evaluated at build time.

In the following snippet, we updated the PrintExceptionAttribute aspect and added the possibility to specify from the custom attribute constructor which type of exception should be traced.

C#
[PSerializable]
    public class PrintExceptionAttribute : OnExceptionAspect
    {
        Type type;

    public PrintExceptionAttribute() : this(typeof(Exception))
    {
    }

        public PrintExceptionAttribute (Type type)
            : base()
        {
            this.type = type;
        }

        // Method invoked at build time.
        // Should return the type of exceptions to be handled. 
        public override Type GetExceptionType(MethodBase method)
        {
            return this.type;
        }


        public override void OnException(MethodExecutionArgs args)
        {
            Console.WriteLine(args.Exception.Message);
        }
    }

Example:

C#
class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    [PrintException(typeof(IOException)]
    public void StoreName(string path)
    {
        File.WriteAllText( path, string.Format( “{0} {1}”, this.FirstName, this.LastName ) );
    }

}
Note Note

If the aspect needs to handle several types of exception, the GetExceptionType should return a common base type, and the OnException implementation should be modified to dynamically handle different types of exception.

Ignoring exceptions

The FlowBehavior member of MethodExecutionArgs in the exception handler’s OnException(MethodExecutionArgs) method, can be set to ignore an exception. Note however that ignoring exceptions is generally dangerous and not recommended. In practice, it’s only safe to ignore exceptions in event handlers (e.g. to display a message in a WPF form) and in thread entry points.

Exceptions can be ignored by setting the FlowBehavior to Return:

C#
[PSerializable]
public class PrintAndIgnoreExceptionAttribute : OnExceptionAspect
{

    public override void OnException(MethodExecutionArgs args)
    {
        Console.WriteLine(args.Exception.Message);
        args.FlowBehavior = FlowBehavior.Return;
    }
 }

If a method returns a value then the ReturnValue member of args can be set to an object to return. For example, consider the following GetDataLength method in Customer which returns the number of characters read from a file:

C#
class Customer
{
    [PrintException(typeof(IOException))]
    public int GetDataLength(string path)
    {
return File.ReadAllText(path).Length;
    }

}

We can then modify the OnException(MethodExecutionArgs) method of PrintAndIgnoreExceptionAttribute to return an integer with a value of -1:

C#
public override void OnException(MethodExecutionArgs args)
{
    Console.WriteLine(args.Exception.Message);
    args.FlowBehavior = FlowBehavior.Return;
    args.ReturnValue = -1;
}
Replacing exceptions

Many times an exception must be exposed to the user, either by allowing the original exception to be rethrown, or by throwing a new exception. This can be done by setting FlowBehavior as follows:

  • FlowBehavior.RethrowException: rethrows the original exception after the exception handler exits. This is the default behavior for the OnException(MethodExecutionArgs) advise.

  • FlowBehavior.ThrowException: throws a new exception once the exception handler exits. This is useful when details of the original exception should be hidden from the user or when a more meaningful exception is to be shown instead. When throwing a new exception, a new exception object must be assigned to the Exception member of MethodExecutionArgs. The following snippet shows the creation of a new BusinessExceptionAttribute which throws a BusinessException containing a description of the cause:

    C#
    [PSerializable]
    public sealed class BusinesssExceptionAttribute : OnExceptionAspect
    {
        public override void OnException(MethodExecutionArgs args)
        {
            .
            .
            .
            args.FlowBehavior = FlowBehavior.ThrowException;        
            args.Exception = new BusinessException("Bad Arguments", new Exception("One or more arguments were null. Use the id " + guid.ToString() + " for more information"));
       }
    }
    
    class BusinessException : Exception
    {
           public BusinessException(string message, Exception innerException) : base(message, innerException)
           {            
           }
    }
Displaying the method arguments on exception

When an exception is thrown, it can be useful to view and display the parameter values that were passed into the method where the exception occurred. These values can be retrieved by iterating through the Arguments property of the args parameter of the OnException(MethodExecutionArgs)method. In the following snippet, OnException(MethodExecutionArgs) has been modified to iterate through all exception values, and to concatenate them into a string. If a null value is encountered, then the code embeds the word null into the string. This string is then displayed as the message of the NullReferenceException which is rethrown:

C#
public override void OnException(MethodExecutionArgs args)
  {
      string parameterValues = "";

      foreach (object arg in args.Arguments)
      {
          if (parameterValues.Length > 0)
          {
              parameterValues += ", ";
          }

          if (arg != null)
          {
              parameterValues += arg.ToString();
          }
          else
          {
              parameterValues += "null";
          }
      }

          Console.WriteLine( “Exception {0} in {1}.{2} invoked with arguments {3}”, args.Exception.GetType().Name, args.Method.DeclaringType.FullName, args.Method.Name, parameterValues );
   }
}
Note Note

The Arguments field of args cannot be directly viewed in the debugger. The Arguments field must be referenced by another object in order to be viewable in the debugger.

See Also