In Getting started: overriding fields and properties, you learned the basics of the OverrideFieldOrPropertyAspect class. In this section, we will cover more advanced scenarios.
Accessing the metadata of the overridden field or property
The metadata of the overridden field or property can be accessed from the template accessors on the meta.Target.FieldOrProperty property. This property provides all information about the field or property's name, type, and custom attributes. For instance, the member name is available on meta.Target.FieldOrProperty.Name
and its type on meta.Target.FieldOrProperty.Type
.
meta.Target.FieldOrProperty
exposes the current field or property as an IFieldOrProperty, which reveals characteristics common to fields and properties.meta.Target.Field
exposes the current field as an IField, but will throw an exception if the target is not a field.meta.Target.Property
exposes the current field as an IProperty, but will throw an exception if the target is not a property.meta.Target.Method
exposes the current accessor method. This works even if the target is a field because Metalama creates pseudo methods to represent field accessors.
To access the value of the field or property, you can use the meta.Target.FieldOrProperty.Value
expression both in reading and writing. In the setter template, meta.Target.Parameters[0].Value
gives you the value of the value
parameter.
Example: Resolving dependencies on the fly
The following example is a simplified implementation of the service locator pattern.
The Import
aspect overrides the getter of a property to make a call to a global service locator. The type of the service is determined from the type of the field or property, using meta.Target.FieldOrProperty.Type
. The dependency is not stored, so the service locator must be called every time the property is evaluated.
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.GlobalImport;
5
6internal class ImportAttribute : OverrideFieldOrPropertyAspect
7{
8 public override dynamic? OverrideProperty
9 {
10 get
11 => ServiceLocator.ServiceProvider.GetService(
12 meta.Target.FieldOrProperty.Type.ToType() );
13
14 set
15 => throw new NotSupportedException(
16 $"{meta.Target.FieldOrProperty.Name} should not be set from source code." );
17 }
18}
1using System;
2using System.Collections.Generic;
3
4namespace Doc.GlobalImport;
5
6internal class Foo
7{
8 [Import]
9 private IFormatProvider? FormatProvider { get; }
10}
11
12internal class ServiceLocator : IServiceProvider
13{
14 private static readonly ServiceLocator _instance = new();
15 private readonly Dictionary<Type, object> _services = new();
16
17 public static IServiceProvider ServiceProvider => _instance;
18
19 object? IServiceProvider.GetService( Type serviceType )
20 {
21 this._services.TryGetValue( serviceType, out var value );
22
23 return value;
24 }
25
26 public static void AddService<T>( T service ) where T : class
27 => _instance._services[typeof(T)] = service;
28}
1using System;
2using System.Collections.Generic;
3
4namespace Doc.GlobalImport;
5
6internal class Foo
7{
8 [Import]
9 private IFormatProvider? FormatProvider
10 {
11 get
12 {
13 return (IFormatProvider?)ServiceLocator.ServiceProvider.GetService(typeof(IFormatProvider));
14 }
15
16 init
17 {
18 throw new NotSupportedException("FormatProvider should not be set from source code.");
19 }
20 }
21}
22
23internal class ServiceLocator : IServiceProvider
24{
25 private static readonly ServiceLocator _instance = new();
26 private readonly Dictionary<Type, object> _services = new();
27
28 public static IServiceProvider ServiceProvider => _instance;
29
30 object? IServiceProvider.GetService(Type serviceType)
31 {
32 this._services.TryGetValue(serviceType, out var value);
33
34 return value;
35 }
36
37 public static void AddService<T>(T service) where T : class
38 => _instance._services[typeof(T)] = service;
39}
Example: Resolving dependencies on the fly and storing the result
This example builds on the previous one, but the dependency is stored in the field or property after it has been retrieved from the service provider for the first time.
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.GlobalImportWithSetter;
5
6internal class ImportAttribute : OverrideFieldOrPropertyAspect
7{
8 public override dynamic? OverrideProperty
9 {
10 get
11 {
12 // Gets the current value of the field or property.
13 var service = meta.Proceed();
14
15 if ( service == null )
16 {
17 // Call the service provider.
18 service =
19 meta.Cast(
20 meta.Target.FieldOrProperty.Type,
21 ServiceLocator.ServiceProvider.GetService(
22 meta.Target.FieldOrProperty.Type.ToType() ) );
23
24 // Set the field or property to the new value.
25 meta.Target.FieldOrProperty.Value = service;
26 }
27
28 return service;
29 }
30
31 set => throw new NotSupportedException();
32 }
33}
1using System;
2using System.Collections.Generic;
3
4namespace Doc.GlobalImportWithSetter;
5
6internal class Foo
7{
8 [Import]
9 private IFormatProvider? _formatProvider;
10}
11
12internal class ServiceLocator : IServiceProvider
13{
14 private static readonly ServiceLocator _instance = new();
15 private readonly Dictionary<Type, object> _services = new();
16
17 public static IServiceProvider ServiceProvider => _instance;
18
19 object? IServiceProvider.GetService( Type serviceType )
20 {
21 this._services.TryGetValue( serviceType, out var value );
22
23 return value;
24 }
25
26 public static void AddService<T>( T service ) where T : class
27 => _instance._services[typeof(T)] = service;
28}
1using System;
2using System.Collections.Generic;
3
4namespace Doc.GlobalImportWithSetter;
5
6internal class Foo
7{
8 private IFormatProvider? _formatProvider1;
9
10 [Import]
11 private IFormatProvider? _formatProvider
12 {
13 get
14 {
15 var service = _formatProvider1;
16 if (service == null)
17 {
18 service = (IFormatProvider?)ServiceLocator.ServiceProvider.GetService(typeof(IFormatProvider));
19 _formatProvider1 = service;
20 }
21
22 return service;
23 }
24
25 set
26 {
27 throw new NotSupportedException();
28 }
29 }
30}
31
32internal class ServiceLocator : IServiceProvider
33{
34 private static readonly ServiceLocator _instance = new();
35 private readonly Dictionary<Type, object> _services = new();
36
37 public static IServiceProvider ServiceProvider => _instance;
38
39 object? IServiceProvider.GetService(Type serviceType)
40 {
41 this._services.TryGetValue(serviceType, out var value);
42
43 return value;
44 }
45
46 public static void AddService<T>(T service) where T : class
47 => _instance._services[typeof(T)] = service;
48}
Overriding several fields or properties from the same aspect
Similar to methods, to override one or more fields or properties from a single aspect, your aspect needs to implement the BuildAspect method exposed on builder.Advice
. Your implementation must then call the builder.Advice.Override method.
Alternatively, you can call the builder.Advice.OverrideAccessors method, which accepts one or two accessor templates, i.e., one template method for the getter and/or one other method for the setter.
Using a property template
The first argument of Override
is the IFieldOrProperty that you want to override. This field or property must be in the type targeted by the current aspect instance.
The second argument of Override
is the name of the template property. This property must exist in the aspect class and, additionally:
- the template property must be annotated with the
[Template]
attribute, - the template property must be of type
dynamic
(dynamically-typed template), or a type compatible with the type of the overridden property (strongly-typed template). - the template property can have a setter, a getter, or both. If one accessor is not specified in the template, the corresponding accessor in the target code will not be overridden.
Example: registry-backed class
The following aspect overrides properties so that they are written to and read from the Windows registry.
1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using Microsoft.Win32;
5using System;
6using System.Linq;
7
8namespace Doc.RegistryStorage;
9
10internal class RegistryStorageAttribute : TypeAspect
11{
12 public string Key { get; }
13
14 public RegistryStorageAttribute( string key )
15 {
16 this.Key = "HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\" + key;
17 }
18
19 public override void BuildAspect( IAspectBuilder<INamedType> builder )
20 {
21 foreach ( var property in builder.Target.FieldsAndProperties.Where(
22 p => p is { IsImplicitlyDeclared: false, IsAutoPropertyOrField: true } ) )
23 {
24 builder.With( property ).Override( nameof(this.OverrideProperty) );
25 }
26 }
27
28 [Template]
29 private dynamic? OverrideProperty
30 {
31 get
32 {
33 var value = Registry.GetValue( this.Key, meta.Target.FieldOrProperty.Name, null );
34
35 if ( value != null )
36 {
37 return Convert.ChangeType( value, meta.Target.FieldOrProperty.Type.ToType() );
38 }
39 else
40 {
41 return default;
42 }
43 }
44
45 set
46 {
47 var stringValue = Convert.ToString( value );
48 Registry.SetValue( this.Key, meta.Target.FieldOrProperty.Name, stringValue );
49 meta.Proceed();
50 }
51 }
52}
1namespace Doc.RegistryStorage;
2
3[RegistryStorage( "Animals" )]
4internal class Animals
5{
6 public int Turtles { get; set; }
7
8 public int Cats { get; set; }
9
10 public int All => this.Turtles + this.Cats;
11}
1using System;
2using Microsoft.Win32;
3
4namespace Doc.RegistryStorage;
5
6[RegistryStorage("Animals")]
7internal class Animals
8{
9 private int _turtles;
10
11 public int Turtles
12 {
13 get
14 {
15 var value = Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Turtles", null);
16 if (value != null)
17 {
18 return (int)Convert.ChangeType(value, typeof(int));
19 }
20 else
21 {
22 return default;
23 }
24 }
25
26 set
27 {
28 var stringValue = Convert.ToString(value);
29 Registry.SetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Turtles", stringValue);
30 _turtles = value;
31 }
32 }
33
34 private int _cats;
35
36 public int Cats
37 {
38 get
39 {
40 var value = Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Cats", null);
41 if (value != null)
42 {
43 return (int)Convert.ChangeType(value, typeof(int));
44 }
45 else
46 {
47 return default;
48 }
49 }
50
51 set
52 {
53 var stringValue = Convert.ToString(value);
54 Registry.SetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Cats", stringValue);
55 _cats = value;
56 }
57 }
58
59 public int All => this.Turtles + this.Cats;
60}
Example: string normalization
This example illustrates a strongly-typed property template with a single accessor that uses the meta.Target.FieldOrProperty.Value
expression to access the underlying field or property.
The following aspect can be applied to fields or properties of type string
. It overrides the setter to trim and lowercase the assigned value.
1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4
5namespace Doc.Normalize;
6
7internal class NormalizeAttribute : FieldOrPropertyAspect
8{
9 public override void BuildAspect( IAspectBuilder<IFieldOrProperty> builder )
10 {
11 builder.Override( nameof(this.OverrideProperty) );
12 }
13
14 [Template]
15 private string? OverrideProperty
16 {
17 set => meta.Target.FieldOrProperty.Value = value?.Trim().ToLowerInvariant();
18 }
19}
1namespace Doc.Normalize;
2
3internal class Foo
4{
5 [Normalize]
6 public string? Property { get; set; }
7}
1namespace Doc.Normalize;
2
3internal class Foo
4{
5 private string? _property;
6
7 [Normalize]
8 public string? Property
9 {
10 get
11 {
12 return _property;
13 }
14
15 set
16 {
17 _property = value?.Trim().ToLowerInvariant();
18 }
19 }
20}
Using an accessor template
Advising fields or properties with the Override
method has the following limitations over the use of OverrideAccessors
:
- You cannot choose a template for each accessor separately.
- You cannot have generic templates.
To alleviate these limitations, you can use the method OverrideAccessors and provide one or two method templates: a getter template and/or a setter template.
The templates must fulfill the following conditions:
- Both templates must be annotated with the
[Template]
attribute. - The getter template must be of signature
T Getter()
, whereT
is eitherdynamic
or a type compatible with the target field or property. - The setter template must be of signature
void Setter(T value)
, where the namevalue
of the first parameter is mandatory.