How do I intercept a method call in C#?

Viewed 102431

For a given class I would like to have tracing functionality i.e. I would like to log every method call (method signature and actual parameter values) and every method exit (just the method signature).

How do I accomplish this assuming that:

  • I don't want to use any 3rd party AOP libraries for C#,
  • I don't want to add duplicate code to all the methods that I want to trace,
  • I don't want to change the public API of the class - users of the class should be able to call all the methods in exactly the same way.

To make the question more concrete let's assume there are 3 classes:

 public class Caller 
 {
     public static void Call() 
     {
         Traced traced = new Traced();
         traced.Method1();
         traced.Method2(); 
     }
 }

 public class Traced 
 {
     public void Method1(String name, Int32 value) { }

     public void Method2(Object object) { }
 }

 public class Logger
 {
     public static void LogStart(MethodInfo method, Object[] parameterValues);

     public static void LogEnd(MethodInfo method);
 }

How do I invoke Logger.LogStart and Logger.LogEnd for every call to Method1 and Method2 without modifying the Caller.Call method and without adding the calls explicitly to Traced.Method1 and Traced.Method2?

Edit: What would be the solution if I'm allowed to slightly change the Call method?

16 Answers

C# is not an AOP oriented language. It has some AOP features and you can emulate some others but making AOP with C# is painful.

I looked up for ways to do exactly what you wanted to do and I found no easy way to do it.

As I understand it, this is what you want to do:

[Log()]
public void Method1(String name, Int32 value);

and in order to do that you have two main options

  1. Inherit your class from MarshalByRefObject or ContextBoundObject and define an attribute which inherits from IMessageSink. This article has a good example. You have to consider nontheless that using a MarshalByRefObject the performance will go down like hell, and I mean it, I'm talking about a 10x performance lost so think carefully before trying that.

  2. The other option is to inject code directly. In runtime, meaning you'll have to use reflection to "read" every class, get its attributes and inject the appropiate call (and for that matter I think you couldn't use the Reflection.Emit method as I think Reflection.Emit wouldn't allow you to insert new code inside an already existing method). At design time this will mean creating an extension to the CLR compiler which I have honestly no idea on how it's done.

The final option is using an IoC framework. Maybe it's not the perfect solution as most IoC frameworks works by defining entry points which allow methods to be hooked but, depending on what you want to achive, that might be a fair aproximation.

The simplest way to achieve that is probably to use PostSharp. It injects code inside your methods based on the attributes that you apply to it. It allows you to do exactly what you want.

Another option is to use the profiling API to inject code inside the method, but that is really hardcore.

If you write a class - call it Tracing - that implements the IDisposable interface, you could wrap all method bodies in a

Using( Tracing tracing = new Tracing() ){ ... method body ...}

In the Tracing class you could the handle the logic of the traces in the constructor/Dispose method, respectively, in the Tracing class to keep track of the entering and exiting of the methods. Such that:

    public class Traced 
    {
        public void Method1(String name, Int32 value) {
            using(Tracing tracer = new Tracing()) 
            {
                [... method body ...]
            }
        }

        public void Method2(Object object) { 
            using(Tracing tracer = new Tracing())
            {
                [... method body ...]
            }
        }
    }

I don't know a solution but my approach would be as follows.

Decorate the class (or its methods) with a custom attribute. Somewhere else in the program, let an initialization function reflect all types, read the methods decorated with the attributes and inject some IL code into the method. It might actually be more practical to replace the method by a stub that calls LogStart, the actual method and then LogEnd. Additionally, I don't know if you can change methods using reflection so it might be more practical to replace the whole type.

You could potentially use the GOF Decorator Pattern, and 'decorate' all classes that need tracing.

It's probably only really practical with an IOC container (but as pointer out earlier you may want to consider method interception if you're going to go down the IOC path).

Maybe it's to late for this answer but here it goes.

What you are looking to achieve is built in MediatR library.

This is my RequestLoggerBehaviour which intercepts all calls to my business layer.

namespace SmartWay.Application.Behaviours
{
    public class RequestLoggerBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
    {
        private readonly ILogger _logger;
        private readonly IAppSession _appSession;
        private readonly ICreateLogGrain _createLogGrain;

        public RequestLoggerBehaviour(ILogger<TRequest> logger, IAppSession appSession, IClusterClient clusterClient)
        {
            _logger = logger;
            _appSession = appSession;
            _createLogGrain = clusterClient.GetGrain<ICreateLogGrain>(Guid.NewGuid());
        }

        public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
        {
            var name = typeof(TRequest).Name;
            _logger.LogInformation($"SmartWay request started: ClientId: {_appSession.ClientId} UserId: {_appSession.UserId} Operation: {name} Request: {request}");

            var response = await next();

            _logger.LogInformation($"SmartWay request ended: ClientId: {_appSession.ClientId} UserId: {_appSession.UserId} Operation: {name} Request: {request}");

            return response;
        }
    }
}

You can also create performance behaviours to trace methods that take too long to execute for example.

Having clean architecture (MediatR) on your business layer will allow you to keep your code clean while you enforce SOLID principles.

You can see how it works here: https://youtu.be/5OtUm1BLmG0?t=1

  1. Write your own AOP library.
  2. Use reflection to generate a logging proxy over your instances (not sure if you can do it without changing some part of your existing code).
  3. Rewrite the assembly and inject your logging code (basically the same as 1).
  4. Host the CLR and add logging at this level (i think this is the hardest solution to implement, not sure if you have the required hooks in the CLR though).
Related