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
6public 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}
Source Code
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4
5namespace Doc.OverrideMethodDefaultTemplateAllKinds;

6
7public 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}
Transformed Code
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4using Metalama.Framework.RunTime;
5
6namespace Doc.OverrideMethodDefaultTemplateAllKinds;
7
8public 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}
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
8public 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(
51                    $"{meta.Target.Method.Name}: intercepted {enumerator.Current}." );
52
53                yield return enumerator.Current;
54            }
55        }
56
57        Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
58    }
59
60    public override async IAsyncEnumerable<dynamic?> OverrideAsyncEnumerableMethod()
61    {
62        Console.WriteLine( $"{meta.Target.Method.Name}: start" );
63
64        await foreach ( var item in meta.ProceedAsyncEnumerable() )
65        {
66            Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {item}." );
67
68            yield return item;
69        }
70
71        Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
72    }
73
74    public override async IAsyncEnumerator<dynamic?> OverrideAsyncEnumeratorMethod()
75    {
76        Console.WriteLine( $"{meta.Target.Method.Name}: start" );
77
78        await using ( var enumerator = meta.ProceedAsyncEnumerator() )
79        {
80            while ( await enumerator.MoveNextAsync() )
81            {
82                Console.WriteLine(
83                    $"{meta.Target.Method.Name}: intercepted {enumerator.Current}." );
84
85                yield return enumerator.Current;
86            }
87        }
88
89        Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
90    }
91}
Source Code
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4
5namespace Doc.OverrideMethodSpecificTemplateAllKinds;
6
7public 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}
Transformed Code
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4
5namespace Doc.OverrideMethodSpecificTemplateAllKinds;
6
7public 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}
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 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.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using Metalama.Framework.Eligibility;
5using System;
6
7namespace Doc.OverrideMethodAspect_;
8
9[AttributeUsage( AttributeTargets.Method )]
10public abstract class OverrideMethodAspect : Attribute, IAspect<IMethod>
11{



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


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


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




22    public abstract dynamic? OverrideMethod();
23}
24
25#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
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.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 has 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 production-ready implementation should also wrap properties.

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

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 AdviserExtensions.Override and MethodTemplateSelector for details.