Metalama 1.0 / / Metalama Documentation / Conceptual Documentation / Creating Aspects / Transforming Code / Overriding Methods

Overriding Methods

The simplest and most common aspect is to wrap the hand-written body of a method with some automatically-generated code, but without modifying the method body itself.

You can achieve this thanks to the OverrideMethodAspect abstract class. OverrideMethodAspect is the aspect-oriented implementation of the decorator design pattern for methods.

The simple way: deriving the OverrideMethod abstract class

  1. Add Metalama to your project as described in Installing Metalama: Quick Start.

  2. Create a new class derived from the OverrideMethodAspect abstract class. This class will be a custom attribute, so it is a good idea to name it with the Attribute suffix.

  3. Implement the OverrideMethod() method in plain C#. This method will serve as a template defining the way the aspect overrides the hand-written target method.

    • To insert code or expressions that depend on the target method of the aspect (such as the method name or the parameter type), use the meta API.
    • Where the original implementation must be invoked, call the meta.Proceed method.
  4. The aspect is a custom attribute. To transform a method using the aspect, just add the aspect custom attribute to the method.

Example: an empty OverrideMethod aspect

The following code shows an empty OverrideMethodAspect, which does not do anything:

using Metalama.Framework.Aspects;

namespace Doc
{
    public class EmptyOverrideMethodAttribute : OverrideMethodAspect
    {
        public override dynamic? OverrideMethod()
        {
            return meta.Proceed();
        }
    }
}
using Metalama.Framework.Aspects;

namespace Doc
{
#pragma warning disable CS0067
    public class EmptyOverrideMethodAttribute : OverrideMethodAspect
    {
        public override dynamic? OverrideMethod() => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");

    }
#pragma warning restore CS0067
}

Accessing the metadata and parameters of the overridden method

The metadata of the method being overridden are available from the template method on the meta.Target.Method property . This property gives you all information about the name, type, parameters and custom attributes of the method. For instance, the metadata of method parameters are exposed on meta.Target.Method.Parameters. But note that only metadata are exposed there.

To access the parameter values, you need to access meta.Target.Parameters. For instance:

  • meta.Target.Parameters[0].Value gives you the value of the first parameter,
  • meta.Target.Parameters.Values.ToArray() creates an object[] array with all parameter values,
  • meta.Target.Parameters["a"].Value = 5 sets the a parameter to 5.

Example: simple logging

The following code writes a message to the system console before and after the method execution. The text includes the name of the target method.

using Metalama.Framework.Aspects;
using System;

namespace Doc.SimpleLogging
{
    public class SimpleLogAttribute : OverrideMethodAspect
    {
        public override dynamic? OverrideMethod()
        {
            Console.WriteLine( $"Entering {meta.Target.Method}" );

            try
            {
                return meta.Proceed();
            }
            finally
            {
                Console.WriteLine( $"Leaving {meta.Target.Method}" );
            }
        }
    }
}
using System;

namespace Doc.SimpleLogging
{
    internal class Foo
    {
        [SimpleLog]
        public void Method1()
        {
            Console.WriteLine( "Hello, world." );
        }
    }
}
using System;

namespace Doc.SimpleLogging
{
    internal class Foo
    {
        [SimpleLog]
        public void Method1()
        {
            Console.WriteLine($"Entering Foo.Method1()");
            try
            {
                Console.WriteLine("Hello, world.");
                return;
            }
            finally
            {
                Console.WriteLine($"Leaving Foo.Method1()");
            }
        }
    }
}

Invoking the method with different arguments

When you call meta.Proceed, the aspect framework generates a call to the overridden method and passes the parameters it received. If the parameter value has been changed thanks to a statement like meta.Target.Parameters["a"].Value = 5, the modified value will be passed.

If you want to invoke the method with a totally different set of arguments, you can do it using meta.Target.Method.Invoke.

Note

Invoking a method with ref or out parameters is not yet supported.

Overriding async and iterator methods

By default, the OverrideMethod() method is used as a template for all methods, including async and iterator methods.

To make the default template work naturally even for async and iterator methods, calls to meta.Proceed() and return statements are interpreted differently in each situation in order to respect the intent of normal (non-async, non-iterator) code. That is, the default behavior tries to respect the decorator pattern.

Warning

Applying the default OverrideMethod() template to an iterator makes the stream to be buffered into a List<T>. In case of long-running streams, this buffering may be undesirable. In this case, specific iterator templates must be specified (see below).

The following table lists the transformations applied to the meta.Proceed() expression and the return statement when a default template is applied to an async or iterator method:

Target Method Transformation of "meta.Proceed()" Transformation of "return result;"
async await meta.Proceed() return result
IEnumerable<T> iterator RunTimeAspectHelper.Buffer( meta.Proceed() ) returning a List<T> return result
IEnumerator<T> iterator RunTimeAspectHelper.Buffer( meta.Proceed() ) returning a List<T>.Enumerator return result
IAsyncEnumerable<T> async await RunTimeAspectHelper.BufferAsync( meta.Proceed() ) returning an AsyncEnumerableList<T> cs await foreach (var r in result) { yield return r; } yield break;
IAsyncEnumerator<T> async await RunTimeAspectHelper.BufferAsync( meta.Proceed() ) returning an AsyncEnumerableList<T>.AsyncEnumerator cs await using ( result ) { while (await result.MoveNextAsync()) { yield return result.Current; } } yield break;

As you can see, the buffering of iterators is performed by the Buffer and BufferAsync methods.

Example: the default template applied to all kinds of methods

The following example demonstrates the behavior of the default template when applied to different kinds of methods. Note that the output of iterators methods is buffered. This is visible in the program output.

using Metalama.Framework.Aspects;
using System;

namespace Doc.OverrideMethodDefaultTemplateAllKinds
{
    public class LogAttribute : OverrideMethodAspect
    {
        public override dynamic? OverrideMethod()
        {
            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
            var result = meta.Proceed();
            Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );

            return result;
        }
    }
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Doc.OverrideMethodDefaultTemplateAllKinds
{
    public class Program
    {
        [Log]
        public static int NormalMethod()
        {
            return 5;
        }

        [Log]
        public static async Task<int> AsyncMethod()
        {
            Console.WriteLine( "  Task.Yield" );
            await Task.Yield();
            Console.WriteLine( "  Resuming" );

            return 5;
        }

        [Log]
        public static IEnumerable<int> EnumerableMethod()
        {
            Console.WriteLine( "  Yielding 1" );

            yield return 1;

            Console.WriteLine( "  Yielding 2" );

            yield return 2;

            Console.WriteLine( "  Yielding 3" );

            yield return 3;
        }

        [Log]
        public static IEnumerator<int> EnumeratorMethod()
        {
            Console.WriteLine( "  Yielding 1" );

            yield return 1;

            Console.WriteLine( "  Yielding 2" );

            yield return 2;

            Console.WriteLine( "  Yielding 3" );

            yield return 3;
        }

        [Log]
        public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
        {
            await Task.Yield();
            Console.WriteLine( "  Yielding 1" );

            yield return 1;

            Console.WriteLine( "  Yielding 2" );

            yield return 2;

            Console.WriteLine( "  Yielding 3" );

            yield return 3;
        }

        [Log]
        public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
        {
            await Task.Yield();
            Console.WriteLine( "  Yielding 1" );

            yield return 1;

            Console.WriteLine( "  Yielding 2" );

            yield return 2;

            Console.WriteLine( "  Yielding 3" );

            yield return 3;
        }

        public static async Task Main()
        {
            NormalMethod();

            await AsyncMethod();

            foreach ( var a in EnumerableMethod() )
            {
                Console.WriteLine( $" Received {a} from EnumerableMethod" );
            }

            Console.WriteLine( "---" );

            var enumerator = EnumeratorMethod();

            while ( enumerator.MoveNext() )
            {
                Console.WriteLine( $" Received {enumerator.Current} from EnumeratorMethod" );
            }

            Console.WriteLine( "---" );

            await foreach ( var a in AsyncEnumerableMethod() )
            {
                Console.WriteLine( $" Received {a} from AsyncEnumerableMethod" );
            }

            Console.WriteLine( "---" );
            var asyncEnumerator = AsyncEnumeratorMethod();

            while ( await asyncEnumerator.MoveNextAsync() )
            {
                Console.WriteLine( $" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod" );
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Metalama.Framework.RunTime;

namespace Doc.OverrideMethodDefaultTemplateAllKinds
{
    public class Program
    {
        [Log]
        public static int NormalMethod()
        {
            Console.WriteLine($"NormalMethod: start");
            int result;
            result = 5;
            goto __aspect_return_1;
        __aspect_return_1:
            Console.WriteLine($"NormalMethod: returning {result}.");
            return result;
        }

        [Log]
        public static async Task<int> AsyncMethod()
        {
            Console.WriteLine($"AsyncMethod: start");
            var result = await AsyncMethod_Source();
            Console.WriteLine($"AsyncMethod: returning {result}.");
            return result;
        }

        private static async Task<int> AsyncMethod_Source()
        {
            Console.WriteLine("  Task.Yield");
            await Task.Yield();
            Console.WriteLine("  Resuming");

            return 5;
        }

        [Log]
        public static IEnumerable<int> EnumerableMethod()
        {
            Console.WriteLine($"EnumerableMethod: start");
            var result = EnumerableMethod_Source().Buffer();
            Console.WriteLine($"EnumerableMethod: returning {result}.");
            return result;
        }

        private static IEnumerable<int> EnumerableMethod_Source()
        {
            Console.WriteLine("  Yielding 1");

            yield return 1;

            Console.WriteLine("  Yielding 2");

            yield return 2;

            Console.WriteLine("  Yielding 3");

            yield return 3;
        }

        [Log]
        public static IEnumerator<int> EnumeratorMethod()
        {
            Console.WriteLine($"EnumeratorMethod: start");
            var result = EnumeratorMethod_Source().Buffer();
            Console.WriteLine($"EnumeratorMethod: returning {result}.");
            return result;
        }

        private static IEnumerator<int> EnumeratorMethod_Source()
        {
            Console.WriteLine("  Yielding 1");

            yield return 1;

            Console.WriteLine("  Yielding 2");

            yield return 2;

            Console.WriteLine("  Yielding 3");

            yield return 3;
        }

        [Log]
        public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
        {
            Console.WriteLine($"AsyncEnumerableMethod: start");
            var result = await AsyncEnumerableMethod_Source().BufferAsync();
            Console.WriteLine($"AsyncEnumerableMethod: returning {result}.");
            await foreach (var r in result)
            {
                yield return r;
            }

            yield break;
        }

        private static async IAsyncEnumerable<int> AsyncEnumerableMethod_Source()
        {
            await Task.Yield();
            Console.WriteLine("  Yielding 1");

            yield return 1;

            Console.WriteLine("  Yielding 2");

            yield return 2;

            Console.WriteLine("  Yielding 3");

            yield return 3;
        }

        [Log]
        public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
        {
            Console.WriteLine($"AsyncEnumeratorMethod: start");
            var result = await AsyncEnumeratorMethod_Source().BufferAsync();
            Console.WriteLine($"AsyncEnumeratorMethod: returning {result}.");
            await using (result)
            {
                while (await result.MoveNextAsync())
                {
                    yield return result.Current;
                }
            }

            yield break;
        }

        private static async IAsyncEnumerator<int> AsyncEnumeratorMethod_Source()
        {
            await Task.Yield();
            Console.WriteLine("  Yielding 1");

            yield return 1;

            Console.WriteLine("  Yielding 2");

            yield return 2;

            Console.WriteLine("  Yielding 3");

            yield return 3;
        }

        public static async Task Main()
        {
            NormalMethod();

            await AsyncMethod();

            foreach (var a in EnumerableMethod())
            {
                Console.WriteLine($" Received {a} from EnumerableMethod");
            }

            Console.WriteLine("---");

            var enumerator = EnumeratorMethod();

            while (enumerator.MoveNext())
            {
                Console.WriteLine($" Received {enumerator.Current} from EnumeratorMethod");
            }

            Console.WriteLine("---");

            await foreach (var a in AsyncEnumerableMethod())
            {
                Console.WriteLine($" Received {a} from AsyncEnumerableMethod");
            }

            Console.WriteLine("---");
            var asyncEnumerator = AsyncEnumeratorMethod();

            while (await asyncEnumerator.MoveNextAsync())
            {
                Console.WriteLine($" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod");
            }
        }
    }
}
NormalMethod: start
NormalMethod: returning 5.
AsyncMethod: start
  Task.Yield
  Resuming
AsyncMethod: returning 5.
EnumerableMethod: start
  Yielding 1
  Yielding 2
  Yielding 3
EnumerableMethod: returning System.Collections.Generic.List`1[System.Int32].
 Received 1 from EnumerableMethod
 Received 2 from EnumerableMethod
 Received 3 from EnumerableMethod
---
EnumeratorMethod: start
  Yielding 1
  Yielding 2
  Yielding 3
EnumeratorMethod: returning System.Collections.Generic.List`1+Enumerator[System.Int32].
 Received 1 from EnumeratorMethod
 Received 2 from EnumeratorMethod
 Received 3 from EnumeratorMethod
---
AsyncEnumerableMethod: start
  Yielding 1
  Yielding 2
  Yielding 3
AsyncEnumerableMethod: returning Metalama.Framework.RunTime.AsyncEnumerableList`1[System.Int32].
 Received 1 from AsyncEnumerableMethod
 Received 2 from AsyncEnumerableMethod
 Received 3 from AsyncEnumerableMethod
---
AsyncEnumeratorMethod: start
  Yielding 1
  Yielding 2
  Yielding 3
AsyncEnumeratorMethod: returning Metalama.Framework.RunTime.AsyncEnumerableList`1+AsyncEnumerator[System.Int32].
 Received 1 from AsyncEnumeratorMethod
 Received 2 from AsyncEnumeratorMethod
 Received 3 from AsyncEnumeratorMethod

Implementing a specific template for async or iterator methods

The default template works great most of the time even on async methods and iterators, but it has a few limitations:

  • You cannot use await or yield in the default template.
  • When you call meta.Proceed() in the default template, it causes the complete evaluation of the async method or iterator.

To overcome these limitations, you can implement different variants of the OverrideMethod. For each variant, instead of calling meta.Proceed, you will call a variant of this method that has a relevant return type.

Template Method Proceed Method Description
OverrideAsyncMethod() ProceedAsync() Applies to all async methods, including async iterators, except if a more specific template is implemented.
OverrideEnumerableMethod() ProceedEnumerable() Applies to iterator methods returning an IEnumerable<T> or IEnumerable.
OverrideEnumeratorMethod() ProceedEnumerator() Applies to iterator methods returning an IEnumerator<T> or IEnumerator.
OverrideAsyncEnumerableMethod() ProceedAsyncEnumerable() Applies to async iterator methods returning an IAsyncEnumerable<T>.
OverrideAsyncEnumeratorMethod() ProceedAsyncEnumerator() Applies to async iterator methods returning an IAsyncEnumerator<T>.

Note that there is no obligation to implement these methods as async methods or yield-based iterators.

Example: specific templates for all kinds of methods

The following example derives from the previous one implements all specific template methods instead of just the default template methods. Note that now the output of iterators is no longer buffered, because this new version of the aspect supports iterator streaming.

using Metalama.Framework.Aspects;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Doc.OverrideMethodSpecificTemplateAllKinds
{
    public class LogAttribute : OverrideMethodAspect
    {
        public override dynamic? OverrideMethod()
        {
            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
            var result = meta.Proceed();
            Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );

            return result;
        }

        public override async Task<dynamic?> OverrideAsyncMethod()
        {
            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
            var result = await meta.ProceedAsync();
            Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );

            return result;
        }

        public override IEnumerable<dynamic?> OverrideEnumerableMethod()
        {
            Console.WriteLine( $"{meta.Target.Method.Name}: start" );

            foreach ( var item in meta.ProceedEnumerable() )
            {
                Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {item}." );

                yield return item;
            }

            Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
        }

        public override IEnumerator<dynamic?> OverrideEnumeratorMethod()
        {
            Console.WriteLine( $"{meta.Target.Method.Name}: start" );

            using ( var enumerator = meta.ProceedEnumerator() )
            {
                while ( enumerator.MoveNext() )
                {
                    Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {enumerator.Current}." );

                    yield return enumerator.Current;
                }
            }

            Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
        }

        public override async IAsyncEnumerable<dynamic?> OverrideAsyncEnumerableMethod()
        {
            Console.WriteLine( $"{meta.Target.Method.Name}: start" );

            await foreach ( var item in meta.ProceedAsyncEnumerable() )
            {
                Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {item}." );

                yield return item;
            }

            Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
        }

        public override async IAsyncEnumerator<dynamic?> OverrideAsyncEnumeratorMethod()
        {
            Console.WriteLine( $"{meta.Target.Method.Name}: start" );

            await using ( var enumerator = meta.ProceedAsyncEnumerator() )
            {
                while ( await enumerator.MoveNextAsync() )
                {
                    Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {enumerator.Current}." );

                    yield return enumerator.Current;
                }
            }

            Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
        }
    }
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Doc.OverrideMethodSpecificTemplateAllKinds
{
    public class Program
    {
        [Log]
        public static int NormalMethod()
        {
            return 5;
        }

        [Log]
        public static async Task<int> AsyncMethod()
        {
            Console.WriteLine( "  Task.Yield" );
            await Task.Yield();
            Console.WriteLine( "  Resuming" );

            return 5;
        }

        [Log]
        public static IEnumerable<int> EnumerableMethod()
        {
            Console.WriteLine( "  Yielding 1" );

            yield return 1;

            Console.WriteLine( "  Yielding 2" );

            yield return 2;

            Console.WriteLine( "  Yielding 3" );

            yield return 3;
        }

        [Log]
        public static IEnumerator<int> EnumeratorMethod()
        {
            Console.WriteLine( "  Yielding 1" );

            yield return 1;

            Console.WriteLine( "  Yielding 2" );

            yield return 2;

            Console.WriteLine( "  Yielding 3" );

            yield return 3;
        }

        [Log]
        public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
        {
            await Task.Yield();
            Console.WriteLine( "  Yielding 1" );

            yield return 1;

            Console.WriteLine( "  Yielding 2" );

            yield return 2;

            Console.WriteLine( "  Yielding 3" );

            yield return 3;
        }

        [Log]
        public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
        {
            await Task.Yield();
            Console.WriteLine( "  Yielding 1" );

            yield return 1;

            Console.WriteLine( "  Yielding 2" );

            yield return 2;

            Console.WriteLine( "  Yielding 3" );

            yield return 3;
        }

        public static async Task Main()
        {
            NormalMethod();

            await AsyncMethod();

            foreach ( var a in EnumerableMethod() )
            {
                Console.WriteLine( $" Received {a} from EnumerableMethod" );
            }

            Console.WriteLine( "---" );

            var enumerator = EnumeratorMethod();

            while ( enumerator.MoveNext() )
            {
                Console.WriteLine( $" Received {enumerator.Current} from EnumeratorMethod" );
            }

            Console.WriteLine( "---" );

            await foreach ( var a in AsyncEnumerableMethod() )
            {
                Console.WriteLine( $" Received {a} from AsyncEnumerableMethod" );
            }

            Console.WriteLine( "---" );

            var asyncEnumerator = AsyncEnumeratorMethod();

            while ( await asyncEnumerator.MoveNextAsync() )
            {
                Console.WriteLine( $" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod" );
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Doc.OverrideMethodSpecificTemplateAllKinds
{
    public class Program
    {
        [Log]
        public static int NormalMethod()
        {
            Console.WriteLine($"NormalMethod: start");
            int result;
            result = 5;
            goto __aspect_return_1;
        __aspect_return_1:
            Console.WriteLine($"NormalMethod: returning {result}.");
            return result;
        }

        [Log]
        public static async Task<int> AsyncMethod()
        {
            Console.WriteLine($"AsyncMethod: start");
            var result = await AsyncMethod_Source();
            Console.WriteLine($"AsyncMethod: returning {result}.");
            return result;
        }

        private static async Task<int> AsyncMethod_Source()
        {
            Console.WriteLine("  Task.Yield");
            await Task.Yield();
            Console.WriteLine("  Resuming");

            return 5;
        }

        [Log]
        public static IEnumerable<int> EnumerableMethod()
        {
            Console.WriteLine($"EnumerableMethod: start");
            foreach (var item in Program.EnumerableMethod_Source())
            {
                Console.WriteLine($"EnumerableMethod: intercepted {item}.");
                yield return item;
            }

            Console.WriteLine($"EnumerableMethod: completed.");
        }

        private static IEnumerable<int> EnumerableMethod_Source()
        {
            Console.WriteLine("  Yielding 1");

            yield return 1;

            Console.WriteLine("  Yielding 2");

            yield return 2;

            Console.WriteLine("  Yielding 3");

            yield return 3;
        }

        [Log]
        public static IEnumerator<int> EnumeratorMethod()
        {
            Console.WriteLine($"EnumeratorMethod: start");
            using (var enumerator = Program.EnumeratorMethod_Source())
            {
                while (enumerator.MoveNext())
                {
                    Console.WriteLine($"EnumeratorMethod: intercepted {enumerator.Current}.");
                    yield return enumerator.Current;
                }
            }

            Console.WriteLine($"EnumeratorMethod: completed.");
        }

        private static IEnumerator<int> EnumeratorMethod_Source()
        {
            Console.WriteLine("  Yielding 1");

            yield return 1;

            Console.WriteLine("  Yielding 2");

            yield return 2;

            Console.WriteLine("  Yielding 3");

            yield return 3;
        }

        [Log]
        public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
        {
            Console.WriteLine($"AsyncEnumerableMethod: start");
            await foreach (var item in Program.AsyncEnumerableMethod_Source())
            {
                Console.WriteLine($"AsyncEnumerableMethod: intercepted {item}.");
                yield return item;
            }

            Console.WriteLine($"AsyncEnumerableMethod: completed.");
        }

        private static async IAsyncEnumerable<int> AsyncEnumerableMethod_Source()
        {
            await Task.Yield();
            Console.WriteLine("  Yielding 1");

            yield return 1;

            Console.WriteLine("  Yielding 2");

            yield return 2;

            Console.WriteLine("  Yielding 3");

            yield return 3;
        }

        [Log]
        public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
        {
            Console.WriteLine($"AsyncEnumeratorMethod: start");
            await using (var enumerator = Program.AsyncEnumeratorMethod_Source())
            {
                while (await enumerator.MoveNextAsync())
                {
                    Console.WriteLine($"AsyncEnumeratorMethod: intercepted {enumerator.Current}.");
                    yield return enumerator.Current;
                }
            }

            Console.WriteLine($"AsyncEnumeratorMethod: completed.");
        }

        private static async IAsyncEnumerator<int> AsyncEnumeratorMethod_Source()
        {
            await Task.Yield();
            Console.WriteLine("  Yielding 1");

            yield return 1;

            Console.WriteLine("  Yielding 2");

            yield return 2;

            Console.WriteLine("  Yielding 3");

            yield return 3;
        }

        public static async Task Main()
        {
            NormalMethod();

            await AsyncMethod();

            foreach (var a in EnumerableMethod())
            {
                Console.WriteLine($" Received {a} from EnumerableMethod");
            }

            Console.WriteLine("---");

            var enumerator = EnumeratorMethod();

            while (enumerator.MoveNext())
            {
                Console.WriteLine($" Received {enumerator.Current} from EnumeratorMethod");
            }

            Console.WriteLine("---");

            await foreach (var a in AsyncEnumerableMethod())
            {
                Console.WriteLine($" Received {a} from AsyncEnumerableMethod");
            }

            Console.WriteLine("---");

            var asyncEnumerator = AsyncEnumeratorMethod();

            while (await asyncEnumerator.MoveNextAsync())
            {
                Console.WriteLine($" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod");
            }
        }
    }
}
NormalMethod: start
NormalMethod: returning 5.
AsyncMethod: start
  Task.Yield
  Resuming
AsyncMethod: returning 5.
EnumerableMethod: start
  Yielding 1
EnumerableMethod: intercepted 1.
 Received 1 from EnumerableMethod
  Yielding 2
EnumerableMethod: intercepted 2.
 Received 2 from EnumerableMethod
  Yielding 3
EnumerableMethod: intercepted 3.
 Received 3 from EnumerableMethod
EnumerableMethod: completed.
---
EnumeratorMethod: start
  Yielding 1
EnumeratorMethod: intercepted 1.
 Received 1 from EnumeratorMethod
  Yielding 2
EnumeratorMethod: intercepted 2.
 Received 2 from EnumeratorMethod
  Yielding 3
EnumeratorMethod: intercepted 3.
 Received 3 from EnumeratorMethod
EnumeratorMethod: completed.
---
AsyncEnumerableMethod: start
  Yielding 1
AsyncEnumerableMethod: intercepted 1.
 Received 1 from AsyncEnumerableMethod
  Yielding 2
AsyncEnumerableMethod: intercepted 2.
 Received 2 from AsyncEnumerableMethod
  Yielding 3
AsyncEnumerableMethod: intercepted 3.
 Received 3 from AsyncEnumerableMethod
AsyncEnumerableMethod: completed.
---
AsyncEnumeratorMethod: start
  Yielding 1
AsyncEnumeratorMethod: intercepted 1.
 Received 1 from AsyncEnumeratorMethod
  Yielding 2
AsyncEnumeratorMethod: intercepted 2.
 Received 2 from AsyncEnumeratorMethod
  Yielding 3
AsyncEnumeratorMethod: intercepted 3.
 Received 3 from AsyncEnumeratorMethod
AsyncEnumeratorMethod: completed.

Using specific templates for non-async awaitable or non-yield enumerable methods

If you want to use the specific templates for methods that have the correct return type but are not implemented using await or yield, set the UseAsyncTemplateForAnyAwaitable or UseEnumerableTemplateForAnyEnumerable property of the OverrideMethodAspect class to true in the aspect constructor.

Overriding several methods with the same aspect

In the above sections, we have always derived our aspect class from the OverrideMethodAspect abstract class. This class exists for simplificy and convenience. It is merely a shortcut that derives from the Attribute class and implements the IAspect<IMethod> interface. The only thing it does is to add an Override advice to the target of the custom attribute.

Here is the simplified source code of the OverrideMethodAspect class:

using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using Metalama.Framework.Eligibility;
using System;

namespace Doc.OverrideMethodAspect_
{
    // <aspect>
    [AttributeUsage( AttributeTargets.Method )]
    public abstract class OverrideMethodAspect : Attribute, IAspect<IMethod>
    {
        public virtual void BuildAspect( IAspectBuilder<IMethod> builder )
        {
            builder.Advice.Override( builder.Target, nameof(this.OverrideMethod) );
        }

        public virtual void BuildEligibility( IEligibilityBuilder<IMethod> builder )
        {
            builder.ExceptForInheritance().MustBeNonAbstract();
        }

        [Template]
        public abstract dynamic? OverrideMethod();
    }

    // </aspect>
}
using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using Metalama.Framework.Eligibility;
using System;

namespace Doc.OverrideMethodAspect_
{
#pragma warning disable CS0067
    // <aspect>
    [AttributeUsage(AttributeTargets.Method)]
    public abstract class OverrideMethodAspect : Attribute, IAspect<IMethod>
    {
        public virtual void BuildAspect(IAspectBuilder<IMethod> builder) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");


        public virtual void BuildEligibility(IEligibilityBuilder<IMethod> builder) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");


        [Template]
        public abstract dynamic? OverrideMethod();
    }
#pragma warning restore CS0067

    // </aspect>
}

In many cases, you will want your aspect to override many methods. For instance, a synchronized object aspect has to override all public instance methods and wrap them with a lock statement.

To override one or more methods, your aspect needs to implement the BuildAspect method and invoke builder.Advice.Override method.

The first argument of Override is the IMethod that you want to override. This method must be in the type being targeted by the current aspect instance.

The second argument of Override is the name of the template method. This method must exist in the aspect class and, additionally:

  • The template method must be annotated with the [Template] attribute,
  • The template method must have a compatible return type and must have only parameters that exist in the target method with a compatible type. When the type is unknown, dynamic can be used. For instance, the following template method will match any method because it has no parameter (therefore will match any parameter list) and have the universal dynamic return type, which also matches void.

    dynamic? Template()
    

Example: synchronized object

The following aspects wraps all instance methods with a lock( this ) statement.

Note

In a production-ready implementation, you should not lock this but a private field. You can introduce this field as described in Introducing Members. A product-ready implementation should also wrap properties.

using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using System.Linq;

namespace Doc.Synchronized
{
    internal class SynchronizedAttribute : TypeAspect
    {
        public override void BuildAspect( IAspectBuilder<INamedType> builder )
        {
            foreach ( var method in builder.Target.Methods.Where( m => !m.IsStatic ) )
            {
                builder.Advice.Override( method, nameof(this.OverrideMethod) );
            }
        }

        [Template]
        private dynamic? OverrideMethod()
        {
            lock ( meta.This )
            {
                return meta.Proceed();
            }
        }
    }
}
namespace Doc.Synchronized
{
    [Synchronized]
    internal class SynchronizedClass
    {
        private double _total;
        private int _samplesCount;

        public void AddSample( double sample )
        {
            this._samplesCount++;
            this._total += sample;
        }

        public void Reset()
        {
            this._total = 0;
            this._samplesCount = 0;
        }

        public double Average => this._samplesCount / this._total;
    }
}
namespace Doc.Synchronized
{
    [Synchronized]
    internal class SynchronizedClass
    {
        private double _total;
        private int _samplesCount;

        public void AddSample(double sample)
        {
            lock (this)
            {
                this._samplesCount++;
                this._total += sample;
                return;
            }
        }

        public void Reset()
        {
            lock (this)
            {
                this._total = 0;
                this._samplesCount = 0;
                return;
            }
        }

        public double Average => this._samplesCount / this._total;
    }
}

Specifying templates for async and iterator methods

Instead of providing a single template method, you can provide several of them and let the framework choose which one is the most suitable. The principle of this feature is described above. Instead of passing a string to the second argument of OverrideMethod, you can pass a MethodTemplateSelector and initialize it with many templates. See the reference documentation of Override and MethodTemplateSelector for details.

TODO: example