| Severity | Error |
| Reported by | Metalama.Framework |
| Message | The aspect '<AspectName>' cannot override the async iterator method '<TargetMethod>' because the expanded template contains 'yield return' inside a try block with a catch clause, which is not allowed by C#. Use a dedicated async iterator template or restructure the template to avoid wrapping 'meta.Proceed()' in a try-catch block. |
Cause
When an aspect overrides an async iterator method, that is, an async method that returns IAsyncEnumerable or IAsyncEnumerator and uses yield return, and the aspect doesn't provide a dedicated template for async iterators, Metalama applies the default OverrideMethod() template, or the OverrideAsyncMethod() template, and interprets it as an async iterator. The result of meta.Proceed() is buffered, and each return statement of the template becomes a loop of yield return statements.
C# doesn't allow a yield return statement inside a try block that has a catch clause, nor inside the catch or finally blocks of such a statement. Metalama reports LAMA0293 when the expanded template would contain such a statement, typically because the template wraps meta.Proceed() and the return statement in a try-catch block. The first placeholder of the message is the aspect, and the second is the overridden method.
In the following example, the Log aspect catches the exceptions of the overridden method, and it's applied to an async iterator.
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.LAMA0293.Error;
5
6public class LogAttribute : OverrideMethodAspect
7{
8 public override dynamic? OverrideMethod()
9 {
10 try
11 {
12 // For an async iterator, this code becomes 'yield return' statements inside the try block.
13 var result = meta.Proceed();
14
15 return result;
16 }
17 catch ( Exception e )
18 {
19 Console.WriteLine( $"{meta.Target.Method} failed: {e.Message}" );
20
21 throw;
22 }
23 }
24}
25
1using System.Collections.Generic;
2using System.Threading.Tasks;
3
4namespace Doc.LAMA0293.Error;
5
6internal class StockService
7{
8 // Error: the default template wraps the async iterator in a try-catch block.
9 [Log]
Error LAMA0293: The aspect 'Log' cannot override the async iterator method 'StockService.GetPricesAsync(string)' because the expanded template contains 'yield return' inside a try block with a catch clause, which is not allowed by C#. Use a dedicated async iterator template or restructure the template to avoid wrapping 'meta.Proceed()' in a try-catch block.
10 public async IAsyncEnumerable<decimal> GetPricesAsync( string symbol )
11 {
12 await Task.Yield();
13
14 yield return 100.5m;
15 yield return 101.25m;
16 }
17}
18
Resolution
Do one of the following:
- Implement the OverrideAsyncEnumerableMethod() template, or OverrideAsyncEnumeratorMethod() for IAsyncEnumerator, and enumerate ProceedAsyncEnumerable() without a try-catch block around
yield return. The default template remains in use for the other methods. This approach also streams the items instead of buffering them. - Restructure the default template so that meta.Proceed() and the
returnstatement aren't inside a try-catch block. Atryblock with only afinallyclause is allowed. - Exclude async iterator methods from the targets of the aspect, for instance with an eligibility condition.
In the following example, the aspect implements a dedicated template for async iterators.
1using Metalama.Framework.Aspects;
2using System;
3using System.Collections.Generic;
4
5namespace Doc.LAMA0293.Fixed;
6
7public class LogAttribute : OverrideMethodAspect
8{
9 public override dynamic? OverrideMethod()
10 {
11 try
12 {
13 var result = meta.Proceed();
14
15 return result;
16 }
17 catch ( Exception e )
18 {
19 Console.WriteLine( $"{meta.Target.Method} failed: {e.Message}" );
20
21 throw;
22 }
23 }
24
25 // Fixed: async iterators use a dedicated template without a try-catch block around 'yield return'.
26 public override async IAsyncEnumerable<dynamic?> OverrideAsyncEnumerableMethod()
27 {
28 Console.WriteLine( $"Enumerating {meta.Target.Method}." );
29
30 await foreach ( var item in meta.ProceedAsyncEnumerable() )
31 {
32 yield return item;
33 }
34
35 Console.WriteLine( $"{meta.Target.Method} completed." );
36 }
37}
38
1using System.Collections.Generic;
2using System.Threading.Tasks;
3
4namespace Doc.LAMA0293.Fixed;
5
6internal class StockService
7{
8 // Fixed: the aspect uses a dedicated template for async iterators.
9 [Log]
10 public async IAsyncEnumerable<decimal> GetPricesAsync( string symbol )
11 {
12 await Task.Yield();
13
14 yield return 100.5m;
15 yield return 101.25m;
16 }
17}
18
1using System;
2using System.Collections.Generic;
3using System.Threading.Tasks;
4
5namespace Doc.LAMA0293.Fixed;
6
7internal class StockService
8{
9 // Fixed: the aspect uses a dedicated template for async iterators.
10 [Log]
11 public async IAsyncEnumerable<decimal> GetPricesAsync(string symbol)
12 {
13 Console.WriteLine("Enumerating StockService.GetPricesAsync(string).");
14 await foreach (var item in this.GetPricesAsync_Source(symbol))
15 {
16 yield return item;
17 }
18
19 Console.WriteLine("StockService.GetPricesAsync(string) completed.");
20 }
21
22 private async IAsyncEnumerable<decimal> GetPricesAsync_Source(string symbol)
23 {
24 await Task.Yield();
25
26 yield return 100.5m;
27 yield return 101.25m;
28 }
29}
30