Overriding methods
In <xref: simple-override-method>, you have learned the basic technique to replace the implementation of a method with some code defined by the aspect. You have achieved this thanks to the OverrideMethodAspect abstract class, aspect-oriented implementation of the decorator design pattern for methods.
In this article, we will assume that you have read <xref: simple-override-method> and will expose more techniques related to overriding methods.
Accessing the method details
The details of the method being overridden are available from the template method on the meta.Target.Method property. This property gives you all information about the 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 thanks to a statement like meta.Target.Parameters["a"].Value = 5
, the modified value will be passed.
If you want to invoke the method with different arguments, you can do it using meta.Target.Method.Invoke.
Note
Invoking a method with ref
or out
parameters is not yet supported.
Overriding async and iterator methods
By default, the OverrideMethod() method is 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 t respect the intent of ordinary (non-async, non-iterator) code. The default behavior tries to respect the decorator pattern.
Warning
Applying the default OverrideMethod() template to an iterator makes the stream to be buffered into a List<T>
. In the case of long-running streams, this buffering may be undesirable. In this case, specific iterator templates must be specified (see below).
The following table lists the transformations applied to the meta.Proceed()
expression and the return
statement when a default template is applied to an async or iterator method:
Target Method |
Transformation of meta.Proceed();
|
Transformation of return result;
|
---|---|---|
async
|
await meta.Proceed()
|
return result;
|
IEnumerable<T> iterator
|
RunTimeAspectHelper.Buffer( meta.Proceed() )
returning a List<T>
|
return result;
|
IEnumerator<T> iterator
|
RunTimeAspectHelper.Buffer( meta.Proceed() )
returning a List<T>.Enumerator
|
return result;
|
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 iterators methods is buffered. This is visible in the program output.
using Metalama.Framework.Aspects;
using System;
namespace Doc.OverrideMethodDefaultTemplateAllKinds
{
public class LogAttribute : OverrideMethodAspect
{
public override dynamic? OverrideMethod()
{
Console.WriteLine( $"{meta.Target.Method.Name}: start" );
var result = meta.Proceed();
Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );
return result;
}
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Doc.OverrideMethodDefaultTemplateAllKinds
{
public class Program
{
[Log]
public static int NormalMethod()
{
return 5;
}
[Log]
public static async Task<int> AsyncMethod()
{
Console.WriteLine( " Task.Yield" );
await Task.Yield();
Console.WriteLine( " Resuming" );
return 5;
}
[Log]
public static IEnumerable<int> EnumerableMethod()
{
Console.WriteLine( " Yielding 1" );
yield return 1;
Console.WriteLine( " Yielding 2" );
yield return 2;
Console.WriteLine( " Yielding 3" );
yield return 3;
}
[Log]
public static IEnumerator<int> EnumeratorMethod()
{
Console.WriteLine( " Yielding 1" );
yield return 1;
Console.WriteLine( " Yielding 2" );
yield return 2;
Console.WriteLine( " Yielding 3" );
yield return 3;
}
[Log]
public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
{
await Task.Yield();
Console.WriteLine( " Yielding 1" );
yield return 1;
Console.WriteLine( " Yielding 2" );
yield return 2;
Console.WriteLine( " Yielding 3" );
yield return 3;
}
[Log]
public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
{
await Task.Yield();
Console.WriteLine( " Yielding 1" );
yield return 1;
Console.WriteLine( " Yielding 2" );
yield return 2;
Console.WriteLine( " Yielding 3" );
yield return 3;
}
public static async Task Main()
{
NormalMethod();
await AsyncMethod();
foreach ( var a in EnumerableMethod() )
{
Console.WriteLine( $" Received {a} from EnumerableMethod" );
}
Console.WriteLine( "---" );
var enumerator = EnumeratorMethod();
while ( enumerator.MoveNext() )
{
Console.WriteLine( $" Received {enumerator.Current} from EnumeratorMethod" );
}
Console.WriteLine( "---" );
await foreach ( var a in AsyncEnumerableMethod() )
{
Console.WriteLine( $" Received {a} from AsyncEnumerableMethod" );
}
Console.WriteLine( "---" );
var asyncEnumerator = AsyncEnumeratorMethod();
while ( await asyncEnumerator.MoveNextAsync() )
{
Console.WriteLine( $" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod" );
}
}
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Metalama.Framework.RunTime;
namespace Doc.OverrideMethodDefaultTemplateAllKinds
{
public class Program
{
[Log]
public static int NormalMethod()
{
Console.WriteLine("NormalMethod: start");
int result;
result = 5;
Console.WriteLine($"NormalMethod: returning {result}.");
return result;
}
[Log]
public static async Task<int> AsyncMethod()
{
Console.WriteLine("AsyncMethod: start");
var result = await AsyncMethod_Source();
Console.WriteLine($"AsyncMethod: returning {result}.");
return result;
}
private static async Task<int> AsyncMethod_Source()
{
Console.WriteLine(" Task.Yield");
await Task.Yield();
Console.WriteLine(" Resuming");
return 5;
}
[Log]
public static IEnumerable<int> EnumerableMethod()
{
Console.WriteLine("EnumerableMethod: start");
var result = EnumerableMethod_Source().Buffer();
Console.WriteLine($"EnumerableMethod: returning {result}.");
return result;
}
private static IEnumerable<int> EnumerableMethod_Source()
{
Console.WriteLine(" Yielding 1");
yield return 1;
Console.WriteLine(" Yielding 2");
yield return 2;
Console.WriteLine(" Yielding 3");
yield return 3;
}
[Log]
public static IEnumerator<int> EnumeratorMethod()
{
Console.WriteLine("EnumeratorMethod: start");
var result = EnumeratorMethod_Source().Buffer();
Console.WriteLine($"EnumeratorMethod: returning {result}.");
return result;
}
private static IEnumerator<int> EnumeratorMethod_Source()
{
Console.WriteLine(" Yielding 1");
yield return 1;
Console.WriteLine(" Yielding 2");
yield return 2;
Console.WriteLine(" Yielding 3");
yield return 3;
}
[Log]
public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
{
Console.WriteLine("AsyncEnumerableMethod: start");
var result = await AsyncEnumerableMethod_Source().BufferAsync();
Console.WriteLine($"AsyncEnumerableMethod: returning {result}.");
await foreach (var r in result)
{
yield return r;
}
yield break;
}
private static async IAsyncEnumerable<int> AsyncEnumerableMethod_Source()
{
await Task.Yield();
Console.WriteLine(" Yielding 1");
yield return 1;
Console.WriteLine(" Yielding 2");
yield return 2;
Console.WriteLine(" Yielding 3");
yield return 3;
}
[Log]
public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
{
Console.WriteLine("AsyncEnumeratorMethod: start");
var result = await AsyncEnumeratorMethod_Source().BufferAsync();
Console.WriteLine($"AsyncEnumeratorMethod: returning {result}.");
await using (result)
{
while (await result.MoveNextAsync())
{
yield return result.Current;
}
}
yield break;
}
private static async IAsyncEnumerator<int> AsyncEnumeratorMethod_Source()
{
await Task.Yield();
Console.WriteLine(" Yielding 1");
yield return 1;
Console.WriteLine(" Yielding 2");
yield return 2;
Console.WriteLine(" Yielding 3");
yield return 3;
}
public static async Task Main()
{
NormalMethod();
await AsyncMethod();
foreach (var a in EnumerableMethod())
{
Console.WriteLine($" Received {a} from EnumerableMethod");
}
Console.WriteLine("---");
var enumerator = EnumeratorMethod();
while (enumerator.MoveNext())
{
Console.WriteLine($" Received {enumerator.Current} from EnumeratorMethod");
}
Console.WriteLine("---");
await foreach (var a in AsyncEnumerableMethod())
{
Console.WriteLine($" Received {a} from AsyncEnumerableMethod");
}
Console.WriteLine("---");
var asyncEnumerator = AsyncEnumeratorMethod();
while (await asyncEnumerator.MoveNextAsync())
{
Console.WriteLine($" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod");
}
}
}
}
NormalMethod: start NormalMethod: returning 5. AsyncMethod: start Task.Yield Resuming AsyncMethod: returning 5. EnumerableMethod: start Yielding 1 Yielding 2 Yielding 3 EnumerableMethod: returning System.Collections.Generic.List`1[System.Int32]. Received 1 from EnumerableMethod Received 2 from EnumerableMethod Received 3 from EnumerableMethod --- EnumeratorMethod: start Yielding 1 Yielding 2 Yielding 3 EnumeratorMethod: returning System.Collections.Generic.List`1+Enumerator[System.Int32]. Received 1 from EnumeratorMethod Received 2 from EnumeratorMethod Received 3 from EnumeratorMethod --- AsyncEnumerableMethod: start Yielding 1 Yielding 2 Yielding 3 AsyncEnumerableMethod: returning Metalama.Framework.RunTime.AsyncEnumerableList`1[System.Int32]. Received 1 from AsyncEnumerableMethod Received 2 from AsyncEnumerableMethod Received 3 from AsyncEnumerableMethod --- AsyncEnumeratorMethod: start Yielding 1 Yielding 2 Yielding 3 AsyncEnumeratorMethod: returning Metalama.Framework.RunTime.AsyncEnumerableList`1+AsyncEnumerator[System.Int32]. Received 1 from AsyncEnumeratorMethod Received 2 from AsyncEnumeratorMethod Received 3 from AsyncEnumeratorMethod
Implementing a specific template for async or iterator methods
The default template works very 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.
using Metalama.Framework.Aspects;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Doc.OverrideMethodSpecificTemplateAllKinds
{
public class LogAttribute : OverrideMethodAspect
{
public override dynamic? OverrideMethod()
{
Console.WriteLine( $"{meta.Target.Method.Name}: start" );
var result = meta.Proceed();
Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );
return result;
}
public override async Task<dynamic?> OverrideAsyncMethod()
{
Console.WriteLine( $"{meta.Target.Method.Name}: start" );
var result = await meta.ProceedAsync();
Console.WriteLine( $"{meta.Target.Method.Name}: returning {result}." );
return result;
}
public override IEnumerable<dynamic?> OverrideEnumerableMethod()
{
Console.WriteLine( $"{meta.Target.Method.Name}: start" );
foreach ( var item in meta.ProceedEnumerable() )
{
Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {item}." );
yield return item;
}
Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
}
public override IEnumerator<dynamic?> OverrideEnumeratorMethod()
{
Console.WriteLine( $"{meta.Target.Method.Name}: start" );
using ( var enumerator = meta.ProceedEnumerator() )
{
while ( enumerator.MoveNext() )
{
Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {enumerator.Current}." );
yield return enumerator.Current;
}
}
Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
}
public override async IAsyncEnumerable<dynamic?> OverrideAsyncEnumerableMethod()
{
Console.WriteLine( $"{meta.Target.Method.Name}: start" );
await foreach ( var item in meta.ProceedAsyncEnumerable() )
{
Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {item}." );
yield return item;
}
Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
}
public override async IAsyncEnumerator<dynamic?> OverrideAsyncEnumeratorMethod()
{
Console.WriteLine( $"{meta.Target.Method.Name}: start" );
await using ( var enumerator = meta.ProceedAsyncEnumerator() )
{
while ( await enumerator.MoveNextAsync() )
{
Console.WriteLine( $"{meta.Target.Method.Name}: intercepted {enumerator.Current}." );
yield return enumerator.Current;
}
}
Console.WriteLine( $"{meta.Target.Method.Name}: completed." );
}
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Doc.OverrideMethodSpecificTemplateAllKinds
{
public class Program
{
[Log]
public static int NormalMethod()
{
return 5;
}
[Log]
public static async Task<int> AsyncMethod()
{
Console.WriteLine( " Task.Yield" );
await Task.Yield();
Console.WriteLine( " Resuming" );
return 5;
}
[Log]
public static IEnumerable<int> EnumerableMethod()
{
Console.WriteLine( " Yielding 1" );
yield return 1;
Console.WriteLine( " Yielding 2" );
yield return 2;
Console.WriteLine( " Yielding 3" );
yield return 3;
}
[Log]
public static IEnumerator<int> EnumeratorMethod()
{
Console.WriteLine( " Yielding 1" );
yield return 1;
Console.WriteLine( " Yielding 2" );
yield return 2;
Console.WriteLine( " Yielding 3" );
yield return 3;
}
[Log]
public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
{
await Task.Yield();
Console.WriteLine( " Yielding 1" );
yield return 1;
Console.WriteLine( " Yielding 2" );
yield return 2;
Console.WriteLine( " Yielding 3" );
yield return 3;
}
[Log]
public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
{
await Task.Yield();
Console.WriteLine( " Yielding 1" );
yield return 1;
Console.WriteLine( " Yielding 2" );
yield return 2;
Console.WriteLine( " Yielding 3" );
yield return 3;
}
public static async Task Main()
{
NormalMethod();
await AsyncMethod();
foreach ( var a in EnumerableMethod() )
{
Console.WriteLine( $" Received {a} from EnumerableMethod" );
}
Console.WriteLine( "---" );
var enumerator = EnumeratorMethod();
while ( enumerator.MoveNext() )
{
Console.WriteLine( $" Received {enumerator.Current} from EnumeratorMethod" );
}
Console.WriteLine( "---" );
await foreach ( var a in AsyncEnumerableMethod() )
{
Console.WriteLine( $" Received {a} from AsyncEnumerableMethod" );
}
Console.WriteLine( "---" );
var asyncEnumerator = AsyncEnumeratorMethod();
while ( await asyncEnumerator.MoveNextAsync() )
{
Console.WriteLine( $" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod" );
}
}
}
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Doc.OverrideMethodSpecificTemplateAllKinds
{
public class Program
{
[Log]
public static int NormalMethod()
{
Console.WriteLine("NormalMethod: start");
int result;
result = 5;
Console.WriteLine($"NormalMethod: returning {result}.");
return result;
}
[Log]
public static async Task<int> AsyncMethod()
{
Console.WriteLine("AsyncMethod: start");
var result = await AsyncMethod_Source();
Console.WriteLine($"AsyncMethod: returning {result}.");
return result;
}
private static async Task<int> AsyncMethod_Source()
{
Console.WriteLine(" Task.Yield");
await Task.Yield();
Console.WriteLine(" Resuming");
return 5;
}
[Log]
public static IEnumerable<int> EnumerableMethod()
{
Console.WriteLine("EnumerableMethod: start");
foreach (var item in Program.EnumerableMethod_Source())
{
Console.WriteLine($"EnumerableMethod: intercepted {item}.");
yield return item;
}
Console.WriteLine("EnumerableMethod: completed.");
}
private static IEnumerable<int> EnumerableMethod_Source()
{
Console.WriteLine(" Yielding 1");
yield return 1;
Console.WriteLine(" Yielding 2");
yield return 2;
Console.WriteLine(" Yielding 3");
yield return 3;
}
[Log]
public static IEnumerator<int> EnumeratorMethod()
{
Console.WriteLine("EnumeratorMethod: start");
using (var enumerator = Program.EnumeratorMethod_Source())
{
while (enumerator.MoveNext())
{
Console.WriteLine($"EnumeratorMethod: intercepted {enumerator.Current}.");
yield return enumerator.Current;
}
}
Console.WriteLine("EnumeratorMethod: completed.");
}
private static IEnumerator<int> EnumeratorMethod_Source()
{
Console.WriteLine(" Yielding 1");
yield return 1;
Console.WriteLine(" Yielding 2");
yield return 2;
Console.WriteLine(" Yielding 3");
yield return 3;
}
[Log]
public static async IAsyncEnumerable<int> AsyncEnumerableMethod()
{
Console.WriteLine("AsyncEnumerableMethod: start");
await foreach (var item in Program.AsyncEnumerableMethod_Source())
{
Console.WriteLine($"AsyncEnumerableMethod: intercepted {item}.");
yield return item;
}
Console.WriteLine("AsyncEnumerableMethod: completed.");
}
private static async IAsyncEnumerable<int> AsyncEnumerableMethod_Source()
{
await Task.Yield();
Console.WriteLine(" Yielding 1");
yield return 1;
Console.WriteLine(" Yielding 2");
yield return 2;
Console.WriteLine(" Yielding 3");
yield return 3;
}
[Log]
public static async IAsyncEnumerator<int> AsyncEnumeratorMethod()
{
Console.WriteLine("AsyncEnumeratorMethod: start");
await using (var enumerator = Program.AsyncEnumeratorMethod_Source())
{
while (await enumerator.MoveNextAsync())
{
Console.WriteLine($"AsyncEnumeratorMethod: intercepted {enumerator.Current}.");
yield return enumerator.Current;
}
}
Console.WriteLine("AsyncEnumeratorMethod: completed.");
}
private static async IAsyncEnumerator<int> AsyncEnumeratorMethod_Source()
{
await Task.Yield();
Console.WriteLine(" Yielding 1");
yield return 1;
Console.WriteLine(" Yielding 2");
yield return 2;
Console.WriteLine(" Yielding 3");
yield return 3;
}
public static async Task Main()
{
NormalMethod();
await AsyncMethod();
foreach (var a in EnumerableMethod())
{
Console.WriteLine($" Received {a} from EnumerableMethod");
}
Console.WriteLine("---");
var enumerator = EnumeratorMethod();
while (enumerator.MoveNext())
{
Console.WriteLine($" Received {enumerator.Current} from EnumeratorMethod");
}
Console.WriteLine("---");
await foreach (var a in AsyncEnumerableMethod())
{
Console.WriteLine($" Received {a} from AsyncEnumerableMethod");
}
Console.WriteLine("---");
var asyncEnumerator = AsyncEnumeratorMethod();
while (await asyncEnumerator.MoveNextAsync())
{
Console.WriteLine($" Received {asyncEnumerator.Current} from AsyncEnumeratorMethod");
}
}
}
}
NormalMethod: start NormalMethod: returning 5. AsyncMethod: start Task.Yield Resuming AsyncMethod: returning 5. EnumerableMethod: start Yielding 1 EnumerableMethod: intercepted 1. Received 1 from EnumerableMethod Yielding 2 EnumerableMethod: intercepted 2. Received 2 from EnumerableMethod Yielding 3 EnumerableMethod: intercepted 3. Received 3 from EnumerableMethod EnumerableMethod: completed. --- EnumeratorMethod: start Yielding 1 EnumeratorMethod: intercepted 1. Received 1 from EnumeratorMethod Yielding 2 EnumeratorMethod: intercepted 2. Received 2 from EnumeratorMethod Yielding 3 EnumeratorMethod: intercepted 3. Received 3 from EnumeratorMethod EnumeratorMethod: completed. --- AsyncEnumerableMethod: start Yielding 1 AsyncEnumerableMethod: intercepted 1. Received 1 from AsyncEnumerableMethod Yielding 2 AsyncEnumerableMethod: intercepted 2. Received 2 from AsyncEnumerableMethod Yielding 3 AsyncEnumerableMethod: intercepted 3. Received 3 from AsyncEnumerableMethod AsyncEnumerableMethod: completed. --- AsyncEnumeratorMethod: start Yielding 1 AsyncEnumeratorMethod: intercepted 1. Received 1 from AsyncEnumeratorMethod Yielding 2 AsyncEnumeratorMethod: intercepted 2. Received 2 from AsyncEnumeratorMethod Yielding 3 AsyncEnumeratorMethod: intercepted 3. Received 3 from AsyncEnumeratorMethod AsyncEnumeratorMethod: completed.
Using specific templates for non-async awaitable or non-yield enumerable methods
If you want to use the specific templates for methods that have the correct return type but are not implemented using await
or yield
, set the UseAsyncTemplateForAnyAwaitable
or UseEnumerableTemplateForAnyEnumerable property of the OverrideMethodAspect class to true
in the aspect constructor.
Overriding several methods with the same aspect
In the above sections, we have always derived our aspect class from the OverrideMethodAspect abstract class. This class exists for 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:
using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using Metalama.Framework.Eligibility;
using System;
namespace Doc.OverrideMethodAspect_
{
[AttributeUsage( AttributeTargets.Method )]
public abstract class OverrideMethodAspect : Attribute, IAspect<IMethod>
{
public virtual void BuildAspect( IAspectBuilder<IMethod> builder )
{
builder.Advice.Override( builder.Target, nameof(this.OverrideMethod) );
}
public virtual void BuildEligibility( IEligibilityBuilder<IMethod> builder )
{
builder.ExceptForInheritance().MustNotBeAbstract();
}
[Template]
public abstract dynamic? OverrideMethod();
}
}
using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using Metalama.Framework.Eligibility;
using System;
namespace Doc.OverrideMethodAspect_
{
#pragma warning disable CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
[AttributeUsage(AttributeTargets.Method)]
public abstract class OverrideMethodAspect : Attribute, IAspect<IMethod>
{
public virtual void BuildAspect(IAspectBuilder<IMethod> builder) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
public virtual void BuildEligibility(IEligibilityBuilder<IMethod> builder) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
[Template]
public abstract dynamic? OverrideMethod();
}
#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
}
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 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 product-ready implementation should also wrap properties.
using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using System.Linq;
namespace Doc.Synchronized
{
internal class SynchronizedAttribute : TypeAspect
{
public override void BuildAspect( IAspectBuilder<INamedType> builder )
{
foreach ( var method in builder.Target.Methods.Where( m => !m.IsStatic ) )
{
builder.Advice.Override( method, nameof(this.OverrideMethod) );
}
}
[Template]
private dynamic? OverrideMethod()
{
lock ( meta.This )
{
return meta.Proceed();
}
}
}
}
namespace Doc.Synchronized
{
[Synchronized]
internal class SynchronizedClass
{
private double _total;
private int _samplesCount;
public void AddSample( double sample )
{
this._samplesCount++;
this._total += sample;
}
public void Reset()
{
this._total = 0;
this._samplesCount = 0;
}
public double Average => this._samplesCount / this._total;
}
}
namespace Doc.Synchronized
{
[Synchronized]
internal class SynchronizedClass
{
private double _total;
private int _samplesCount;
public void AddSample(double sample)
{
lock (this)
{
this._samplesCount++;
this._total += sample;
return;
}
}
public void Reset()
{
lock (this)
{
this._total = 0;
this._samplesCount = 0;
return;
}
}
public double Average => this._samplesCount / this._total;
}
}
Specifying templates for async and iterator methods
Instead of providing a single template method, you can 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.