Metalama / / Conceptual documentation / Creating aspects / Creating simple aspects / Overriding methods

Getting started: overriding a method

Overriding a method is the most straightforward aspect you can imagine. The implementation of your aspect will simply replace the original implementation of the aspect. Let's see how it works.

Creating your first method aspect

To create an aspect that overrides methods:

  1. Add the Metalama.Framework package to your project.

  2. Create a class and make it inherit the OverrideMethodAspect class. This class will be a custom attribute, so naming it with the Attribute suffix is a good idea.

  3. Override the OverrideMethod method.

  4. In your OverrideMethod implementation, call meta.Proceed where the original method should be invoked.

    Note

    meta is a special class. It can almost be considered a keyword that lets you tap into the meta-model of the code you are dealing with. In this case, calling meta.Proceed is equivalent to calling the method that your aspect is overriding.

  5. Apply your new aspect to any relevant method as a custom attribute.

Example: trivial logging

The following aspect overrides the target method and adds a call to Console.WriteLine to write a message before the method is executed.

using System;
using Metalama.Framework.Aspects;

namespace Doc.SimpleLog
{
    public class LogAttribute : OverrideMethodAspect
    {
        public override dynamic? OverrideMethod()
        {
            // Enhancing the method.
            Console.WriteLine($"Simply logging a method...");

            // Let the method do its own thing.
            return meta.Proceed();
        }

    }
}

using System;

namespace Doc.SimpleLog
{
    public class Program
    {
        [Log]
        public static void SayHello(string name)
        {
            Console.WriteLine($"Hello {name}");
        }

        public static void Main(string[] args)
        {
            SayHello("Your Majesty");
        }
    }
}
using System;

namespace Doc.SimpleLog
{
    public class Program
    {
        [Log]
        public static void SayHello(string name)
        {
            Console.WriteLine($"Simply logging a method...");
            Console.WriteLine($"Hello {name}");
            return;
        }

        public static void Main(string[] args)
        {
            SayHello("Your Majesty");
        }
    }
}

To see the effect of the aspect on the code in this documentation, switch to the Transformed Code tab above. In Visual Studio, you can preview the transformed code thanks to the Diff Preview feature. See Understanding your aspect-oriented code for details.

As you can see, OverrideMethodAspect does exactly what the name suggests: to override the method. So if you put your aspect on a method, the aspect code will be executed instead of the code of the target method. Therefore, the following line of code gets executed first:

            Console.WriteLine($"Simply logging a method..." );

          

Then, thanks to the call to meta.Proceed, the original method code is executed.

Arguably, this aspect does not do much yet, so let's make it more useful.

Example: retrying upon exception

In the previous chapter, you used the built-in aspect Retry. Here is how it is implemented.

using System;
using System.Threading;
using Metalama.Framework.Aspects;

namespace Doc.RetryFew
{
    public class RetryAttribute : OverrideMethodAspect
    {
        public override dynamic OverrideMethod()
        {
            for (var i = 0; ; i++)
            {
                try
                {
                    return meta.Proceed();
                }
                catch (Exception e) when (i < 3)
                {
                    Console.WriteLine($"{e.Message}. Retrying in 100 ms.");
                    Thread.Sleep(100);
                }
            }
        }
    }
}
using System;
using System.Net;

namespace Doc.RetryFew
{
    public class Exchange
    {

        [Retry]
        public double GetExchangeRate()
        {
            // Simulates a call to an exchange rate web API.
            // Sometimes the connection may fail.

            var n = new Random(5).Next(20);
            if ( n % 2 == 0 )
            {
                return 0.5345;
            }
            else
            {
                throw new WebException( "The service is not available." );
            }
        }

    }
    public class Program
    {
        public static void Main(string[] args)
        {
            var x = new Exchange();
            var rate = x.GetExchangeRate();
            Console.WriteLine(rate);
        }
    }
}
using System;
using System.Net;
using System.Threading;

namespace Doc.RetryFew
{
    public class Exchange
    {

        [Retry]
        public double GetExchangeRate()
        {
            for (var i = 0; ; i++)
            {
                try
                {
                    // Simulates a call to an exchange rate web API.
                    // Sometimes the connection may fail.

                    var n = new Random(5).Next(20);
                    if (n % 2 == 0)
                    {
                        return 0.5345;
                    }
                    else
                    {
                        throw new WebException("The service is not available.");
                    }
                }
                catch (Exception e) when (i < 3)
                {
                    Console.WriteLine($"{e.Message}. Retrying in 100 ms.");
                    Thread.Sleep(100);
                }
            }
        }

    }
    public class Program
    {
        public static void Main(string[] args)
        {
            var x = new Exchange();
            var rate = x.GetExchangeRate();
            Console.WriteLine(rate);
        }
    }
}

Note how the overridden implementation in the aspect retries the method being overridden. In this example, the number of retries is hard-coded.

Example: authorizing the current user

The following example shows how to verify the current user's identity before executing a method.

using System.Security;
using Metalama.Framework.Aspects;

namespace Doc.Authorize
{
    public class AuthorizeAttribute : OverrideMethodAspect
    {
        public override dynamic OverrideMethod()
        {
            var currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;

            if (currentUser == "MrAllmighty")
            {
                return meta.Proceed();
            }
            else
            {
                throw new SecurityException("This method can only be called by MrAllmighty.");
            }
        }
    }
}
using System;


namespace Doc.Authorize
{
    
    public class Program
    {
        [Authorize]
        public static void SaveIdentityDetails()
        {
            // A sensitive method that should 
            // ideally only be called by the current user.
            Console.WriteLine("Saving identity details...");
        }

        public static void Main(string[] args)
        {
             SaveIdentityDetails();
        }
    }
}
using System;
using System.Security;
using System.Security.Principal;

namespace Doc.Authorize
{

    public class Program
    {
        [Authorize]
        public static void SaveIdentityDetails()
        {
            var currentUser = WindowsIdentity.GetCurrent().Name;
            if (currentUser == "MrAllmighty")
            {
                // A sensitive method that should 
                // ideally only be called by the current user.
                Console.WriteLine("Saving identity details...");
                return;
            }
            else
            {
                throw new SecurityException("This method can only be called by MrAllmighty.");
            }
        }

        public static void Main(string[] args)
        {
            SaveIdentityDetails();
        }
    }
}

Adding context from the target method

None of the examples above contained anything specific to the method to which the aspect was applied. Even the logging aspect wrote a generic message.

Instead of writing a generic message to the console, we will now write a text that includes the name of the target method.

You can get to the target of the aspect by calling the meta.Target.Method property, which exposes all relevant information about the current method: its name, its list of parameters and their types, and so on.

So if you want to get to the name of the method you are targeting from the aspect code, you can do so by calling meta.Target.Method.Name. You can get the qualified name of the method by calling the meta.Target.Method.ToDisplayString() method.

Now let's see how this information can be used to enhance the logging aspect that is already created.

The following code shows how this can be used:

Example: including the method name in the log

using Metalama.Framework.Aspects;
using System;
namespace Doc.Logging
{
    public class LogAttribute : OverrideMethodAspect
    {
        public override dynamic? OverrideMethod()
        {
            var targetMethodName = meta.Target.Method.ToDisplayString();

            try
            {
                Console.WriteLine($"Started {targetMethodName}");
                return meta.Proceed();
            }
            finally
            {
                Console.WriteLine($"Finished {targetMethodName}");
            }
        }
    }
}
namespace Doc.Logging
{
    public class Program
    {
        [Log]
        public static void SayHello(string name)
        {
            System.Console.WriteLine($"Hello {name}");
        }
        public static void Main(string[] args)
        {
            SayHello("Your Majesty");
        }
    }
}
using System;

namespace Doc.Logging
{
    public class Program
    {
        [Log]
        public static void SayHello(string name)
        {
            try
            {
                Console.WriteLine("Started Program.SayHello(string)");
                System.Console.WriteLine($"Hello {name}");
                return;
            }
            finally
            {
                Console.WriteLine("Finished Program.SayHello(string)");
            }
        }
        public static void Main(string[] args)
        {
            SayHello("Your Majesty");
        }
    }
}

Example: profiling a method

When you need to find out which method call is taking time, the first thing you generally do is to decorate the method with print statements to find out how much time each call takes. The following aspect lets you wrap that in an aspect. And whenever you need to track the calls to a method, you just have to place this aspect (in the form of the attribute) on the method as shown in the Target code.

using Metalama.Framework.Aspects;
using System;
using System.Diagnostics;


namespace Doc.Profile
{
    public class ProfileAttribute : OverrideMethodAspect
    {
        public override dynamic OverrideMethod()
        {
            var sw = Stopwatch.StartNew();

            try
            {
                return meta.Proceed();
            }
            finally
            {
                var name = meta.Target.Method.ToDisplayString();
                Console.WriteLine( $"{name} executed in {sw.ElapsedMilliseconds} ms." );
            }
        }   
    }
}
using System;
using System.Threading;

namespace Doc.Profile
{

    public class Program
    {
        [Profile]
        public static int SimulatedDelay()
        {
            // Simulating a random delay between 500 ms to 2 secs
            Thread.Sleep(new Random().Next(500,2000));
            return 0;
        }

       
        public static void Main(string[] args)
        {
            SimulatedDelay();
        }
    }
}
using System;
using System.Diagnostics;
using System.Threading;

namespace Doc.Profile
{

    public class Program
    {
        [Profile]
        public static int SimulatedDelay()
        {
            var sw = Stopwatch.StartNew();
            try
            {
                // Simulating a random delay between 500 ms to 2 secs
                Thread.Sleep(new Random().Next(500, 2000));
                return 0;
            }
            finally
            {
                Console.WriteLine($"Program.SimulatedDelay() executed in {sw.ElapsedMilliseconds} ms.");
            }
        }


        public static void Main(string[] args)
        {
            SimulatedDelay();
        }
    }
}

Going deeper

If you want to go deeper with method overrides, consider jumping to the following articles:

  • In this article, you have seen how to use meta.Proceed and meta.Target.Method.Name in your templates. You can write much more complex and powerful templates, even doing compile-time if and foreach blocks. To see how you can jump directly to Writing T# templates.

  • To learn how to have different templates for async or iterator methods, or to learn how to override several methods from a single type-level aspect, jump to Overriding methods.