MetalamaConceptual documentationCreating aspectsCreating simple aspectsOverriding methods
Open sandboxFocusImprove this doc

Getting started: overriding a method

Overriding a method is one of the simplest aspects you can implement. Your aspect's implementation will replace the original implementation. Let's discuss how this works.

Creating your first method aspect

To create an aspect that overrides methods, follow these steps:

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

  2. Create a class and inherit the OverrideMethodAspect class. This class will serve as a custom attribute, so it's recommended to name it with the Attribute suffix.

  3. Override the OverrideMethod method.

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

    Note

    meta is a unique class. It can almost be considered a keyword that allows you to interact with the meta-model of the code you are working 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.

1using System;
2using Metalama.Framework.Aspects;
3
4namespace Doc.SimpleLog
5{
6    public class LogAttribute : OverrideMethodAspect
7    {
8        public override dynamic? OverrideMethod()
9        {
10            // Enhancing the method.
11            Console.WriteLine( $"Simply logging a method..." );
12
13            // Let the method do its own thing.
14            return meta.Proceed();
15        }
16    }
17}
Source Code
1using System;
2
3namespace Doc.SimpleLog
4{
5    public class Program
6    {
7        [Log]
8        public static void SayHello( string name )
9        {
10            Console.WriteLine( $"Hello {name}" );
11        }
12

13        public static void Main()
14        {
15            SayHello( "Your Majesty" );
16        }
17    }
18}
Transformed Code
1using System;
2
3namespace Doc.SimpleLog
4{
5    public class Program
6    {
7        [Log]
8        public static void SayHello(string name)
9        {
10            Console.WriteLine($"Simply logging a method...");
11            Console.WriteLine($"Hello {name}");
12        }
13
14        public static void Main()
15        {
16            SayHello("Your Majesty");
17        }
18    }
19}
Simply logging a method...
Hello 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 using the Diff Preview feature. Refer to Understanding your aspect-oriented code for details.

As demonstrated, OverrideMethodAspect does exactly what the name suggests: it overrides the method. If you apply your aspect to a method, the aspect code will be executed instead of the target method's code. Therefore, the following line of code is executed first:

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

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

Admittedly, this aspect does not do much yet. Let's make it more useful.

Example: retrying upon exception

In the previous chapter, you used the built-in aspect Retry. Here is its implementation.

1using System;
2using System.Threading;
3using Metalama.Framework.Aspects;
4
5namespace Doc.RetryFew
6{
7    public class RetryAttribute : OverrideMethodAspect
8    {
9        public override dynamic? OverrideMethod()
10        {
11            for ( var i = 0;; i++ )
12            {
13                try
14                {
15                    return meta.Proceed();
16                }
17                catch ( Exception e ) when ( i < 3 )
18                {
19                    Console.WriteLine( $"{e.Message}. Retrying in 100 ms." );
20                    Thread.Sleep( 100 );
21                }
22            }
23        }
24    }
25}
Source Code
1using System;
2using System.Net;
3
4namespace Doc.RetryFew

5{
6    public class Exchange
7    {
8        [Retry]
9        public double GetExchangeRate()
10        {
11            // Simulates a call to an exchange rate web API.
12            // Sometimes the connection may fail.




13
14            var n = new Random( 5 ).Next( 20 );
15
16            if ( n % 2 == 0 )
17            {
18                return 0.5345;
19            }
20            else
21            {
22                throw new WebException( "The service is not available." );
23            }





24        }


25    }
26
27    public class Program
28    {
29        public static void Main()
30        {
31            var x = new Exchange();
32            var rate = x.GetExchangeRate();
33            Console.WriteLine( rate );
34        }
35    }
36}
Transformed Code
1using System;
2using System.Net;
3using System.Threading;
4
5namespace Doc.RetryFew
6{
7    public class Exchange
8    {
9        [Retry]
10        public double GetExchangeRate()
11        {
12            for (var i = 0; ; i++)
13            {
14                try
15                {
16                    // Simulates a call to an exchange rate web API.
17                    // Sometimes the connection may fail.
18
19                    var n = new Random(5).Next(20);
20
21                    if (n % 2 == 0)
22                    {
23                        return 0.5345;
24                    }
25                    else
26                    {
27                        throw new WebException("The service is not available.");
28                    }
29                }
30                catch (Exception e) when (i < 3)
31                {
32                    Console.WriteLine($"{e.Message}. Retrying in 100 ms.");
33                    Thread.Sleep(100);
34                }
35            }
36        }
37    }
38
39    public class Program
40    {
41        public static void Main()
42        {
43            var x = new Exchange();
44            var rate = x.GetExchangeRate();
45            Console.WriteLine(rate);
46        }
47    }
48}
0.5345

Notice 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 demonstrates how to verify the current user's identity before executing a method.

1using System.Security;
2using Metalama.Framework.Aspects;
3using System.Security.Principal;
4
5namespace Doc.Authorize
6{
7    public class AuthorizeAttribute : OverrideMethodAspect
8    {
9        public override dynamic? OverrideMethod()
10        {
11            var currentUser = WindowsIdentity.GetCurrent().Name;
12
13            if ( currentUser == "MrAllmighty" )
14            {
15                return meta.Proceed();
16            }
17            else
18            {
19                throw new SecurityException( "This method can only be called by MrAllmighty." );
20            }
21        }
22    }
23}
Source Code
1using System;
2
3namespace Doc.Authorize


4{
5    public class Program
6    {
7        [Authorize]
8        public static void SaveIdentityDetails()
9        {
10            // A sensitive method that should 
11            // ideally only be called by the current user.



12            Console.WriteLine( "Saving identity details..." );
13        }
14






15        public static void Main()
16        {
17            try
18            {
19                SaveIdentityDetails();
20            }
21            catch ( Exception e )
22            {
23                Console.Error.WriteLine( e.ToString() );
24            }
25        }
26    }
27}
Transformed Code
1using System;
2using System.Security;
3using System.Security.Principal;
4
5namespace Doc.Authorize
6{
7    public class Program
8    {
9        [Authorize]
10        public static void SaveIdentityDetails()
11        {
12            var currentUser = WindowsIdentity.GetCurrent().Name;
13            if (currentUser == "MrAllmighty")
14            {
15                // A sensitive method that should 
16                // ideally only be called by the current user.
17                Console.WriteLine("Saving identity details...");
18                return;
19            }
20            else
21            {
22                throw new SecurityException("This method can only be called by MrAllmighty.");
23            }
24        }
25
26        public static void Main()
27        {
28            try
29            {
30                SaveIdentityDetails();
31            }
32            catch (Exception e)
33            {
34                Console.Error.WriteLine(e.ToString());
35            }
36        }
37    }
38}
System.Security.SecurityException: This method can only be called by MrAllmighty.
   at Doc.Authorize.Program.SaveIdentityDetails()
   at Doc.Authorize.Program.Main()

Adding context from the target method

None of the above examples contain 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, let's write a text that includes the name of the target method.

You can access 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, etc.

To get the name of the method you are targeting from the aspect code, call meta.Target.Method.Name. You can get the qualified name of the method by calling the meta.Target.Method.ToDisplayString() method.

Let's see how this information can be used to enhance the logging aspect we've already created.

The following code demonstrates how this can be done:

Example: including the method name in the log

1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.Logging
5{
6    public class LogAttribute : OverrideMethodAspect
7    {
8        public override dynamic? OverrideMethod()
9        {
10            var targetMethodName = meta.Target.Method.ToDisplayString();
11
12            try
13            {
14                Console.WriteLine( $"Started {targetMethodName}" );
15
16                return meta.Proceed();
17            }
18            finally
19            {
20                Console.WriteLine( $"Finished {targetMethodName}" );
21            }
22        }
23    }
24}
Source Code
1using System;
2
3namespace Doc.Logging
4{
5    public class Program
6    {
7        [Log]
8        public static void SayHello( string name )
9        {
10            Console.WriteLine( $"Hello {name}" );
11        }








12

13        public static void Main()
14        {
15            SayHello( "Your Majesty" );
16        }
17    }
18}
Transformed Code
1using System;
2
3namespace Doc.Logging
4{
5    public class Program
6    {
7        [Log]
8        public static void SayHello(string name)
9        {
10            try
11            {
12                Console.WriteLine("Started Program.SayHello(string)");
13                Console.WriteLine($"Hello {name}");
14                return;
15            }
16            finally
17            {
18                Console.WriteLine("Finished Program.SayHello(string)");
19            }
20        }
21
22        public static void Main()
23        {
24            SayHello("Your Majesty");
25        }
26    }
27}
Started Program.SayHello(string)
Hello Your Majesty
Finished Program.SayHello(string)

Example: profiling a method

When you need to find out which method call is taking time, the first step is usually to decorate the method with print statements to determine how much time each call takes. The following aspect allows you to wrap that in an aspect. Whenever you need to track the calls to a method, just apply this aspect (in the form of the attribute) to the method as shown in the Target code.

1using Metalama.Framework.Aspects;
2using System;
3using System.Diagnostics;
4
5namespace Doc.Profile
6{
7    public class ProfileAttribute : OverrideMethodAspect
8    {
9        public override dynamic? OverrideMethod()
10        {
11            var sw = Stopwatch.StartNew();
12
13            try
14            {
15                return meta.Proceed();
16            }
17            finally
18            {
19                var name = meta.Target.Method.ToDisplayString();
20                Console.WriteLine( $"{name} executed in {sw.ElapsedMilliseconds} ms." );
21            }
22        }
23    }
24}
Source Code
1using System;
2using System.Threading;
3
4namespace Doc.Profile

5{
6    public class Program
7    {
8        [Profile]
9        public static int SimulatedDelay()
10        {
11            // Simulating a random delay between 500 ms to 2 secs
12            Thread.Sleep( new Random().Next( 500, 2000 ) );
13



14            return 0;
15        }
16
17        public static void Main()





18        {
19            SimulatedDelay();
20        }
21    }
22}
Transformed Code
1using System;
2using System.Diagnostics;
3using System.Threading;
4
5namespace Doc.Profile
6{
7    public class Program
8    {
9        [Profile]
10        public static int SimulatedDelay()
11        {
12            var sw = Stopwatch.StartNew();
13            try
14            {
15                // Simulating a random delay between 500 ms to 2 secs
16                Thread.Sleep(new Random().Next(500, 2000));
17
18                return 0;
19            }
20            finally
21            {
22                Console.WriteLine($"Program.SimulatedDelay() executed in {sw.ElapsedMilliseconds} ms.");
23            }
24        }
25
26        public static void Main()
27        {
28            SimulatedDelay();
29        }
30    }
31}
Program.SimulatedDelay() executed in 1351 ms.

Going deeper

If you want to delve deeper into method overrides, consider reading the following articles:

  • In this article, you have learned how to use meta.Proceed and meta.Target.Method.Name in your templates. You can create much more complex and powerful templates, even doing compile-time if and foreach blocks. To learn how, 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.