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 thea
parameter to5
.meta.Target.Parameters.ToValueArray()
creates anobject[]
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; } |
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}
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}
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
oryield
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}
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}
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:
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}
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 universaldynamic
return type, which also matchesvoid
.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}
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}
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.