Search code examples
c#asp.net-coreattributescustom-attributes

Can attributes be used to modify a method's code/behavior?


Let's say you have several methods of the following form (greatly simplified here for illustration purposes):

Snippet 1

public async Task<T> DoSomething<T>()
{
    return await MyApi.DoSomeOperation<T>();
}

You would like to add exception-handling functionality to all such methods, but without needing to write a bunch of repetitive code. In the end, your methods should look (or perform) something like this:

Snippet 2

public async Task<T> DoSomething<T>()
{
    try
    {    
        return await MyApi.DoSomeOperation<T>();
    }
    catch (Exception ex)
    {
        var apiEx = new MyApiException(ex);        
        MyLogger.Log(apiEx);
        throw apiEx;  
    }
}

Is there a way to do this with attributes? For example, could I do something like this?

Snippet 3

public class TryCatchLogThrowAttribute : Attribute
{
    public Type ExceptionType { get; set; }
    public Type LoggerType { get; set; }
    // logic goes here:
}

And then decorate my methods as follows (with the expectation that the actual executed code looks or behaves like Snippet 2 above)?

Snippet 4

[TryCatchLogThrow(typeof(MyApiException), typeof(MyLogger))]
public async Task<T> DoSomething<T>()
{
    return await MyApi.DoSomeOperation<T>();
}

How doable is this?


Solution

  • The simple answer is this: No. Neither C# nor .NET support this out of the box. Attributes are metadata attached to a member or type, but they have to be picked up and used by code in order for them to do anything at all.

    A few attributes influence the compiler directly, a few are not actually stored as attributes but instead influence metadata flags, the rest are simply stored alongside each member of type as metadata.

    In almost all cases, out of the box, they're only available through reflection and thus you have to write additional code to inspect and act upon their presence. The members themselves, however, are oblivious to the presence of these attributes and the code inside does not change depending on it.

    What you're describing is AOP, Aspect Oriented Programming, where you attach aspects, usually cross-cutting behavior, to types or members, such as logging, performance monitoring, error handling, access control, etc. without actually writing the behavior into the members as readable code.

    This is not supported out of the box, but there are products and libraries out there that allows you to do this.

    I can list a few, but this list is not at all exhaustive as it has been a few years since I looked at AOP:

    Both of these allow you, with or without automating it for you, to load an already compiled assembly, decode and take it apart, inject additional code in certain places, then write out a rewritten assembly to disk.

    Of the two I would highly recommend PostSharp as it is by far the easiest of the two to use, but again, it's been a long time since I looked at AOP so there may be more contenders to this throne.