MetalamaConceptual documentationCreating aspectsAdvising codeOverriding methods
Open sandboxFocusImprove this doc

Overriding methods

In Getting started: overriding a method, you learned the basic technique to replace a method's implementation with code defined by the aspect. This was achieved using the OverrideMethodAspect abstract class, an aspect-oriented implementation of the decorator design pattern for methods.

This article assumes you have read Getting started: overriding a method and will expose additional techniques related to overriding methods.

Accessing the method details

The details of the method being overridden are accessible from the template method on the meta.Target.Method property. This property provides information about the method's name, type, parameters, and custom attributes. For instance, the metadata of method parameters is exposed on meta.Target.Method.Parameters.

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

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

Invoking the method with different arguments

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

If you want to invoke the method with different arguments, you can do so 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 selected 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 to respect the intent of ordinary (non-async, non-iterator) code. The default behavior aims to respect the decorator pattern.

Warning

Applying the default OverrideMethod() template to an iterator results in the stream being buffered into a List<T>. In the case of long-running streams, this buffering may be undesirable. In such cases, 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;
async IAsyncEnumerable<T> await RunTimeAspectHelper.BufferAsync( meta.Proceed() ) returning an AsyncEnumerableList<T>
await foreach (var r in result)
{
      yield return r;
}

yield break;
async IAsyncEnumerator<T> await RunTimeAspectHelper.BufferAsync( meta.Proceed() ) returning an AsyncEnumerableList<T>.AsyncEnumerator
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 iterator methods is buffered. This is visible in the program output.

1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.OverrideMethodDefaultTemplateAllKinds
5{
6    public class LogAttribute : OverrideMethodAspect
7    {
8        public override dynamic? OverrideMethod()
9        {
10            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
11            var result = meta.Proceed();
12            Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );
13
14            return result;
15        }
16    }
17}
Source Code
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4
5namespace Doc.OverrideMethodDefaultTemplateAllKinds

6{
7    public class Program
8    {
9        [Log]
10        public static int NormalMethod()
11        {
12            return 5;
13        }
14




15        [Log]
16        public static async Task<int> AsyncMethod()
17        {
18            Console.WriteLine( "  Task.Yield" );
19            await Task.Yield();
20            Console.WriteLine( "  Resuming" );








21
22            return 5;
23        }
24
25        [Log]
26        public static IEnumerable<int> EnumerableMethod()
27        {
28            Console.WriteLine( "  Yielding 1" );
29








30            yield return 1;
31
32            Console.WriteLine( "  Yielding 2" );
33
34            yield return 2;
35
36            Console.WriteLine( "  Yielding 3" );
37
38            yield return 3;
39        }
40
41        [Log]
42        public static IEnumerator<int> EnumeratorMethod()
43        {
44            Console.WriteLine( "  Yielding 1" );
45








46            yield return 1;
47
48            Console.WriteLine( "  Yielding 2" );
49
50            yield return 2;
51
52            Console.WriteLine( "  Yielding 3" );
53
54            yield return 3;
55        }
56
57        [Log]
58        public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
59        {
60            await Task.Yield();
61            Console.WriteLine( "  Yielding 1" );













62
63            yield return 1;
64
65            Console.WriteLine( "  Yielding 2" );
66
67            yield return 2;
68
69            Console.WriteLine( "  Yielding 3" );
70
71            yield return 3;
72        }
73
74        [Log]
75        public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
76        {
77            await Task.Yield();
78            Console.WriteLine( "  Yielding 1" );
















79
80            yield return 1;
81
82            Console.WriteLine( "  Yielding 2" );
83
84            yield return 2;
85
86            Console.WriteLine( "  Yielding 3" );
87
88            yield return 3;
89        }
90
91        public static async Task Main()
92        {
93            NormalMethod();
94
95            await AsyncMethod();
96
97            foreach ( var a in EnumerableMethod() )
98            {
99                Console.WriteLine( $" Received {a} from EnumerableMethod" );
100            }
101
102            Console.WriteLine( "---" );
103
104            var enumerator = EnumeratorMethod();
105
106            while ( enumerator.MoveNext() )
107            {
108                Console.WriteLine( $" Received {enumerator.Current} from EnumeratorMethod" );
109            }
110
111            Console.WriteLine( "---" );
112
113            await foreach ( var a in AsyncEnumerableMethod() )
114            {
115                Console.WriteLine( $" Received {a} from AsyncEnumerableMethod" );
116            }
117
118            Console.WriteLine( "---" );
119            var asyncEnumerator = AsyncEnumeratorMethod();
120
121            while ( await asyncEnumerator.MoveNextAsync() )
122            {
123                Console.WriteLine( $" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod" );
124            }
125        }
126    }
127}
Transformed Code
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4using Metalama.Framework.RunTime;
5
6namespace Doc.OverrideMethodDefaultTemplateAllKinds
7{
8    public class Program
9    {
10        [Log]
11        public static int NormalMethod()
12        {
13            Console.WriteLine("NormalMethod: start");
14            int result;
15            result = 5;
16            Console.WriteLine($"NormalMethod: returning {result}.");
17            return result;
18        }
19
20        [Log]
21        public static async Task<int> AsyncMethod()
22        {
23            Console.WriteLine("AsyncMethod: start");
24            var result = await AsyncMethod_Source();
25            Console.WriteLine($"AsyncMethod: returning {result}.");
26            return result;
27        }
28
29        private static async Task<int> AsyncMethod_Source()
30        {
31            Console.WriteLine("  Task.Yield");
32            await Task.Yield();
33            Console.WriteLine("  Resuming");
34
35            return 5;
36        }
37
38        [Log]
39        public static IEnumerable<int> EnumerableMethod()
40        {
41            Console.WriteLine("EnumerableMethod: start");
42            var result = EnumerableMethod_Source().Buffer();
43            Console.WriteLine($"EnumerableMethod: returning {result}.");
44            return result;
45        }
46
47        private static IEnumerable<int> EnumerableMethod_Source()
48        {
49            Console.WriteLine("  Yielding 1");
50
51            yield return 1;
52
53            Console.WriteLine("  Yielding 2");
54
55            yield return 2;
56
57            Console.WriteLine("  Yielding 3");
58
59            yield return 3;
60        }
61
62        [Log]
63        public static IEnumerator<int> EnumeratorMethod()
64        {
65            Console.WriteLine("EnumeratorMethod: start");
66            var result = EnumeratorMethod_Source().Buffer();
67            Console.WriteLine($"EnumeratorMethod: returning {result}.");
68            return result;
69        }
70
71        private static IEnumerator<int> EnumeratorMethod_Source()
72        {
73            Console.WriteLine("  Yielding 1");
74
75            yield return 1;
76
77            Console.WriteLine("  Yielding 2");
78
79            yield return 2;
80
81            Console.WriteLine("  Yielding 3");
82
83            yield return 3;
84        }
85
86        [Log]
87        public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
88        {
89            Console.WriteLine("AsyncEnumerableMethod: start");
90            var result = await AsyncEnumerableMethod_Source().BufferAsync();
91            Console.WriteLine($"AsyncEnumerableMethod: returning {result}.");
92            await foreach (var r in result)
93            {
94                yield return r;
95            }
96
97            yield break;
98        }
99
100        private static async IAsyncEnumerable<int> AsyncEnumerableMethod_Source()
101        {
102            await Task.Yield();
103            Console.WriteLine("  Yielding 1");
104
105            yield return 1;
106
107            Console.WriteLine("  Yielding 2");
108
109            yield return 2;
110
111            Console.WriteLine("  Yielding 3");
112
113            yield return 3;
114        }
115
116        [Log]
117        public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
118        {
119            Console.WriteLine("AsyncEnumeratorMethod: start");
120            var result = await AsyncEnumeratorMethod_Source().BufferAsync();
121            Console.WriteLine($"AsyncEnumeratorMethod: returning {result}.");
122            await using (result)
123            {
124                while (await result.MoveNextAsync())
125                {
126                    yield return result.Current;
127                }
128            }
129
130            yield break;
131        }
132
133        private static async IAsyncEnumerator<int> AsyncEnumeratorMethod_Source()
134        {
135            await Task.Yield();
136            Console.WriteLine("  Yielding 1");
137
138            yield return 1;
139
140            Console.WriteLine("  Yielding 2");
141
142            yield return 2;
143
144            Console.WriteLine("  Yielding 3");
145
146            yield return 3;
147        }
148
149        public static async Task Main()
150        {
151            NormalMethod();
152
153            await AsyncMethod();
154
155            foreach (var a in EnumerableMethod())
156            {
157                Console.WriteLine($" Received {a} from EnumerableMethod");
158            }
159
160            Console.WriteLine("---");
161
162            var enumerator = EnumeratorMethod();
163
164            while (enumerator.MoveNext())
165            {
166                Console.WriteLine($" Received {enumerator.Current} from EnumeratorMethod");
167            }
168
169            Console.WriteLine("---");
170
171            await foreach (var a in AsyncEnumerableMethod())
172            {
173                Console.WriteLine($" Received {a} from AsyncEnumerableMethod");
174            }
175
176            Console.WriteLine("---");
177            var asyncEnumerator = AsyncEnumeratorMethod();
178
179            while (await asyncEnumerator.MoveNextAsync())
180            {
181                Console.WriteLine($" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod");
182            }
183        }
184    }
185}
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 well 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 should invoke the variant of this method with 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 and implements all specific template methods instead of just the default ones. Note that now the output of iterators is no longer buffered because this new aspect version supports iterator streaming.

1using Metalama.Framework.Aspects;
2using System;
3using System.Collections.Generic;
4using System.Threading.Tasks;
5
6namespace Doc.OverrideMethodSpecificTemplateAllKinds
7{
8    public class LogAttribute : OverrideMethodAspect
9    {
10        public override dynamic? OverrideMethod()
11        {
12            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
13            var result = meta.Proceed();
14            Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );
15
16            return result;
17        }
18
19        public override async Task<dynamic?> OverrideAsyncMethod()
20        {
21            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
22            var result = await meta.ProceedAsync();
23            Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );
24
25            return result;
26        }
27
28        public override IEnumerable<dynamic?> OverrideEnumerableMethod()
29        {
30            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
31
32            foreach ( var item in meta.ProceedEnumerable() )
33            {
34                Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {item}." );
35
36                yield return item;
37            }
38
39            Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
40        }
41
42        public override IEnumerator<dynamic?> OverrideEnumeratorMethod()
43        {
44            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
45
46            using ( var enumerator = meta.ProceedEnumerator() )
47            {
48                while ( enumerator.MoveNext() )
49                {
50                    Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {enumerator.Current}." );
51
52                    yield return enumerator.Current;
53                }
54            }
55
56            Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
57        }
58
59        public override async IAsyncEnumerable<dynamic?> OverrideAsyncEnumerableMethod()
60        {
61            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
62
63            await foreach ( var item in meta.ProceedAsyncEnumerable() )
64            {
65                Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {item}." );
66
67                yield return item;
68            }
69
70            Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
71        }
72
73        public override async IAsyncEnumerator<dynamic?> OverrideAsyncEnumeratorMethod()
74        {
75            Console.WriteLine( $"{meta.Target.Method.Name}: start" );
76
77            await using ( var enumerator = meta.ProceedAsyncEnumerator() )
78            {
79                while ( await enumerator.MoveNextAsync() )
80                {
81                    Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {enumerator.Current}." );
82
83                    yield return enumerator.Current;
84                }
85            }
86
87            Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
88        }
89    }
90}
Source Code
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4
5namespace Doc.OverrideMethodSpecificTemplateAllKinds
6{
7    public class Program
8    {
9        [Log]
10        public static int NormalMethod()
11        {
12            return 5;
13        }
14




15        [Log]
16        public static async Task<int> AsyncMethod()
17        {
18            Console.WriteLine( "  Task.Yield" );
19            await Task.Yield();








20            Console.WriteLine( "  Resuming" );
21
22            return 5;
23        }
24
25        [Log]
26        public static IEnumerable<int> EnumerableMethod()
27        {
28            Console.WriteLine( "  Yielding 1" );
29
30            yield return 1;












31
32            Console.WriteLine( "  Yielding 2" );
33
34            yield return 2;
35
36            Console.WriteLine( "  Yielding 3" );
37
38            yield return 3;
39        }
40
41        [Log]
42        public static IEnumerator<int> EnumeratorMethod()
43        {
44            Console.WriteLine( "  Yielding 1" );
45





46            yield return 1;










47
48            Console.WriteLine( "  Yielding 2" );
49
50            yield return 2;
51
52            Console.WriteLine( "  Yielding 3" );
53
54            yield return 3;
55        }
56
57        [Log]
58        public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
59        {
60            await Task.Yield();
61            Console.WriteLine( "  Yielding 1" );












62
63            yield return 1;
64
65            Console.WriteLine( "  Yielding 2" );
66
67            yield return 2;
68
69            Console.WriteLine( "  Yielding 3" );
70
71            yield return 3;
72        }
73
74        [Log]
75        public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
76        {
77            await Task.Yield();
78            Console.WriteLine( "  Yielding 1" );















79
80            yield return 1;
81
82            Console.WriteLine( "  Yielding 2" );
83
84            yield return 2;
85
86            Console.WriteLine( "  Yielding 3" );
87
88            yield return 3;
89        }
90
91        public static async Task Main()
92        {
93            NormalMethod();
94
95            await AsyncMethod();
96
97            foreach ( var a in EnumerableMethod() )
98            {
99                Console.WriteLine( $" Received {a} from EnumerableMethod" );
100            }
101
102            Console.WriteLine( "---" );
103
104            var enumerator = EnumeratorMethod();
105
106            while ( enumerator.MoveNext() )
107            {
108                Console.WriteLine( $" Received {enumerator.Current} from EnumeratorMethod" );
109            }
110
111            Console.WriteLine( "---" );
112
113            await foreach ( var a in AsyncEnumerableMethod() )
114            {
115                Console.WriteLine( $" Received {a} from AsyncEnumerableMethod" );
116            }
117
118            Console.WriteLine( "---" );
119
120            var asyncEnumerator = AsyncEnumeratorMethod();
121
122            while ( await asyncEnumerator.MoveNextAsync() )
123            {
124                Console.WriteLine( $" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod" );
125            }
126        }
127    }
128}
Transformed Code
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4
5namespace Doc.OverrideMethodSpecificTemplateAllKinds
6{
7    public class Program
8    {
9        [Log]
10        public static int NormalMethod()
11        {
12            Console.WriteLine("NormalMethod: start");
13            int result;
14            result = 5;
15            Console.WriteLine($"NormalMethod: returning {result}.");
16            return result;
17        }
18
19        [Log]
20        public static async Task<int> AsyncMethod()
21        {
22            Console.WriteLine("AsyncMethod: start");
23            var result = await AsyncMethod_Source();
24            Console.WriteLine($"AsyncMethod: returning {result}.");
25            return result;
26        }
27
28        private static async Task<int> AsyncMethod_Source()
29        {
30            Console.WriteLine("  Task.Yield");
31            await Task.Yield();
32            Console.WriteLine("  Resuming");
33
34            return 5;
35        }
36
37        [Log]
38        public static IEnumerable<int> EnumerableMethod()
39        {
40            Console.WriteLine("EnumerableMethod: start");
41            foreach (var item in Program.EnumerableMethod_Source())
42            {
43                Console.WriteLine($"EnumerableMethod: intercepted {item}.");
44                yield return item;
45            }
46
47            Console.WriteLine("EnumerableMethod: completed.");
48        }
49
50        private static IEnumerable<int> EnumerableMethod_Source()
51        {
52            Console.WriteLine("  Yielding 1");
53
54            yield return 1;
55
56            Console.WriteLine("  Yielding 2");
57
58            yield return 2;
59
60            Console.WriteLine("  Yielding 3");
61
62            yield return 3;
63        }
64
65        [Log]
66        public static IEnumerator<int> EnumeratorMethod()
67        {
68            Console.WriteLine("EnumeratorMethod: start");
69            using (var enumerator = Program.EnumeratorMethod_Source())
70            {
71                while (enumerator.MoveNext())
72                {
73                    Console.WriteLine($"EnumeratorMethod: intercepted {enumerator.Current}.");
74                    yield return enumerator.Current;
75                }
76            }
77
78            Console.WriteLine("EnumeratorMethod: completed.");
79        }
80
81        private static IEnumerator<int> EnumeratorMethod_Source()
82        {
83            Console.WriteLine("  Yielding 1");
84
85            yield return 1;
86
87            Console.WriteLine("  Yielding 2");
88
89            yield return 2;
90
91            Console.WriteLine("  Yielding 3");
92
93            yield return 3;
94        }
95
96        [Log]
97        public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
98        {
99            Console.WriteLine("AsyncEnumerableMethod: start");
100            await foreach (var item in Program.AsyncEnumerableMethod_Source())
101            {
102                Console.WriteLine($"AsyncEnumerableMethod: intercepted {item}.");
103                yield return item;
104            }
105
106            Console.WriteLine("AsyncEnumerableMethod: completed.");
107        }
108
109        private static async IAsyncEnumerable<int> AsyncEnumerableMethod_Source()
110        {
111            await Task.Yield();
112            Console.WriteLine("  Yielding 1");
113
114            yield return 1;
115
116            Console.WriteLine("  Yielding 2");
117
118            yield return 2;
119
120            Console.WriteLine("  Yielding 3");
121
122            yield return 3;
123        }
124
125        [Log]
126        public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
127        {
128            Console.WriteLine("AsyncEnumeratorMethod: start");
129            await using (var enumerator = Program.AsyncEnumeratorMethod_Source())
130            {
131                while (await enumerator.MoveNextAsync())
132                {
133                    Console.WriteLine($"AsyncEnumeratorMethod: intercepted {enumerator.Current}.");
134                    yield return enumerator.Current;
135                }
136            }
137
138            Console.WriteLine("AsyncEnumeratorMethod: completed.");
139        }
140
141        private static async IAsyncEnumerator<int> AsyncEnumeratorMethod_Source()
142        {
143            await Task.Yield();
144            Console.WriteLine("  Yielding 1");
145
146            yield return 1;
147
148            Console.WriteLine("  Yielding 2");
149
150            yield return 2;
151
152            Console.WriteLine("  Yielding 3");
153
154            yield return 3;
155        }
156
157        public static async Task Main()
158        {
159            NormalMethod();
160
161            await AsyncMethod();
162
163            foreach (var a in EnumerableMethod())
164            {
165                Console.WriteLine($" Received {a} from EnumerableMethod");
166            }
167
168            Console.WriteLine("---");
169
170            var enumerator = EnumeratorMethod();
171
172            while (enumerator.MoveNext())
173            {
174                Console.WriteLine($" Received {enumerator.Current} from EnumeratorMethod");
175            }
176
177            Console.WriteLine("---");
178
179            await foreach (var a in AsyncEnumerableMethod())
180            {
181                Console.WriteLine($" Received {a} from AsyncEnumerableMethod");
182            }
183
184            Console.WriteLine("---");
185
186            var asyncEnumerator = AsyncEnumeratorMethod();
187
188            while (await asyncEnumerator.MoveNextAsync())
189            {
190                Console.WriteLine($" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod");
191            }
192        }
193    }
194}
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 simplicity and convenience. It is merely a shortcut that derives from the <xref:System.Attribute> class and implements the IAspect<IMethod> interface. The only thing it does is add an Override advice to the target of the custom attribute.

Here is the simplified source code of the OverrideMethodAspect class:

Source Code
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using Metalama.Framework.Eligibility;
4using System;
5
6namespace Doc.OverrideMethodAspect_
7{
8    [AttributeUsage( AttributeTargets.Method )]
9    public abstract class OverrideMethodAspect : Attribute, IAspect<IMethod>
10    {



11        public virtual void BuildAspect( IAspectBuilder<IMethod> builder )
12        {
13            builder.Advice.Override( builder.Target, nameof(this.OverrideMethod) );
14        }
15
16        public virtual void BuildEligibility( IEligibilityBuilder<IMethod> builder )


17        {
18            builder.ExceptForInheritance().MustNotBeAbstract();
19        }
20
21        [Template]
22        public abstract dynamic? OverrideMethod();
23    }
24}
Transformed Code
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using Metalama.Framework.Eligibility;
4using System;
5
6namespace Doc.OverrideMethodAspect_
7{
8
9#pragma warning disable CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
10
11    [AttributeUsage(AttributeTargets.Method)]
12    public abstract class OverrideMethodAspect : Attribute, IAspect<IMethod>
13    {
14        public virtual void BuildAspect(IAspectBuilder<IMethod> builder) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
15


16
17        public virtual void BuildEligibility(IEligibilityBuilder<IMethod> builder) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
18
19
20        [Template]




21        public abstract dynamic? OverrideMethod();
22    }
23
24#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
25
26
27}

You will often 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 must implement the BuildAspect method and invoke the builder.Advice.Override method.

The first argument of Override is the IMethod that you want to override. This method must be in the type 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 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 check any parameter list) and have the universal dynamic return type, which also matches void.

    dynamic? Template()
    

Example: synchronized object

The following aspect 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.

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System.Linq;
4
5namespace Doc.Synchronized
6{
7    internal class SynchronizedAttribute : TypeAspect
8    {
9        public override void BuildAspect( IAspectBuilder<INamedType> builder )
10        {
11            foreach ( var method in builder.Target.Methods.Where( m => !m.IsStatic ) )
12            {
13                builder.Advice.Override( method, nameof(this.OverrideMethod) );
14            }
15        }
16
17        [Template]
18        private dynamic? OverrideMethod()
19        {
20            lock ( meta.This )
21            {
22                return meta.Proceed();
23            }
24        }
25    }
26}
Source Code
1namespace Doc.Synchronized
2{
3    [Synchronized]
4    internal class SynchronizedClass
5    {
6        private double _total;
7        private int _samplesCount;
8
9        public void AddSample( double sample )
10        {
11            this._samplesCount++;
12            this._total += sample;


13        }
14


15        public void Reset()
16        {
17            this._total = 0;
18            this._samplesCount = 0;


19        }
20


21        public double Average => this._samplesCount / this._total;
22    }
23}
Transformed Code
1namespace Doc.Synchronized
2{
3    [Synchronized]
4    internal class SynchronizedClass
5    {
6        private double _total;
7        private int _samplesCount;
8
9        public void AddSample(double sample)
10        {
11            lock (this)
12            {
13                this._samplesCount++;
14                this._total += sample;
15                return;
16            }
17        }
18
19        public void Reset()
20        {
21            lock (this)
22            {
23                this._total = 0;
24                this._samplesCount = 0;
25                return;
26            }
27        }
28
29        public double Average => this._samplesCount / this._total;
30    }
31}

Specifying templates for async and iterator methods

Instead of providing a single template method, you can give several of them and let the framework choose the most suitable one. 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 IAdviceFactory.Override and MethodTemplateSelector for details.