Open sandboxFocusImprove this doc

Example: Report and swallow exceptions

Report and swallow is a last-chance exception-handling strategy, i.e. a strategy for exceptions that could not be handled deeper in the call stack. In general, swallowing a last-chance exception is not a good idea because it may leave your application in an invalid state. Also, using an aspect to implement a last-chance exception handler is generally not a good idea because it is simpler to use the AppDomain.CurrentDomain.UnhandledException event.

However, there are some cases where you need to handle last-chance exceptions with a try...catch block. For instance, when your code is a plug-in in some host application like Office or Visual Studio, exceptions that are not handled by Visual Studio Extensions have a chance to crash the whole Visual Studio without any error message.

In this case, you must implement exception handling at each entry point of your API. This includes:

  • any interface of the host that your plug-in implements,
  • any handlers to host events your plug-in subscribes to,
  • any Winforms/WPF entry point.

Since there can be hundreds of such entry points, it is useful to automate this pattern using an aspect.

In this example, we will assume that we have a host application that defines an interface IPartProvider that plug-ins can implement. Let's see what the aspect does with the code:

Source Code


1public class PartProvider : IPartProvider
2{
3    [ReportAndSwallowExceptions]
4    public string GetPart( string name ) => throw new Exception( "This method has a bug." );














5}
Transformed Code
1using System;
2
3public class PartProvider : IPartProvider
4{
5    [ReportAndSwallowExceptions]
6    public string GetPart( string name ) { try
7        {
8            throw new Exception( "This method has a bug." );}
9        catch (Exception e) when (_exceptionHandler != null && _exceptionHandler.ShouldHandle(e))
10        {
11            _exceptionHandler.Report(e);
12            return default;
13        }
14    }
15private ILastChanceExceptionHandler? _exceptionHandler;
16
17    public PartProvider(ILastChanceExceptionHandler? exceptionHandler = null)
18    {
19        this._exceptionHandler = exceptionHandler;
20    }
21}

As we can see, the ReportAndSwallowExceptions aspect pulls the ILastChanceExceptionHandler from the dependency injection container and adds a try...catch block to the target method.

Infrastructure code

The aspect uses the following interface:

1public interface ILastChanceExceptionHandler
2{
3    /// <summary>
4    /// Determines if an exception should be handled. Typically returns <c>false</c> 
5    /// for exception like <see cref="OperationCanceledException"/>.
6    /// </summary>
7    bool ShouldHandle( Exception e );
8
9    /// <summary>
10    /// Reports the exception to the user or to the crash report service.
11    /// </summary>
12    void Report( Exception e );
13}

Aspect code

The ReportAndSwallowExceptionsAttribute aspect is rather simple:

1using Metalama.Extensions.DependencyInjection;
2using Metalama.Framework.Aspects;
3
4#pragma warning disable CS0649
5
6public class ReportAndSwallowExceptionsAttribute : OverrideMethodAspect
7{
8    [IntroduceDependency( IsRequired = false )]
9    private readonly ILastChanceExceptionHandler? _exceptionHandler;
10
11    public override dynamic? OverrideMethod()
12    {
13        try
14        {
15            return meta.Proceed();
16        }
17        catch ( Exception e ) when ( this._exceptionHandler != null &&
18                                     this._exceptionHandler.ShouldHandle( e ) )
19        {
20            this._exceptionHandler.Report( e );
21
22            return default;
23        }
24    }
25}

If you have read the previous examples, the following notes should be redundant.

The ReportAndSwallowExceptionsAttribute class derives from the OverrideMethodAspect abstract class, which in turn derives from the System.Attribute class. This makes ReportAndSwallowExceptionsAttribute a custom attribute.

The ReportAndSwallowExceptionsAttribute class implements the OverrideMethod method. This method acts like a template. Most of the code in this template is injected into the target method, i.e., the method to which we add the [ReportAndSwallowExceptionsAttribute] custom attribute.

Inside the OverrideMethod implementation, the call to meta.Proceed() has a very special meaning. When the aspect is applied to the target, the call to meta.Proceed() is replaced by the original implementation, with a few syntactic changes to capture the return value.

To remind you that meta.Proceed() has a special meaning, it is colored differently than the rest of the code. If you use Metalama Tools for Visual Studio, you will also enjoy syntax highlighting in this IDE.

Around the call to meta.Proceed(), we have a try...catch exception handler. The catch block has a when clause that should ensure that we do not handle exceptions that are accepted by the host, typically OperationCanceledException. Then, the catch handler simply reports the exception and does not rethrow it.

The [IntroduceDependency] on the top of the _exceptionHandler field does the magic of introducing the field and pulling it from the constructor.