MetalamaConceptual documentationCreating aspectsAdvising codeOverriding fields or properties
Open sandboxFocusImprove this doc

Overriding fields or properties

In Getting started: overriding fields and properties, you learned the basics of the OverrideFieldOrPropertyAspect class. Now, we will cover more advanced scenarios.

Accessing the metadata of the overridden field or property

The metadata of the overridden field or property is accessible 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{
6    internal class ImportAttribute : OverrideFieldOrPropertyAspect
7    {
8        public override dynamic? OverrideProperty
9        {
10            get => ServiceLocator.ServiceProvider.GetService( meta.Target.FieldOrProperty.Type.ToType() );
11
12            set => throw new NotSupportedException( $"{meta.Target.FieldOrProperty.Name} should not be set from source code." );
13        }
14    }
15}
Source Code
1using System;
2using System.Collections.Generic;
3
4namespace Doc.GlobalImport
5{
6    internal class Foo
7    {
8        [Import]
9        private IFormatProvider? FormatProvider { get; }
10    }
11




12    internal 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 => _instance._services[typeof(T)] = service;
27    }
28}
Transformed Code
1using System;
2using System.Collections.Generic;
3
4namespace Doc.GlobalImport
5{
6    internal 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
23    internal 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 => _instance._services[typeof(T)] = service;
38    }
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{
6    internal 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( meta.Target.FieldOrProperty.Type.ToType() ) );
22
23                    // Set the field or property to the new value.
24                    meta.Target.FieldOrProperty.Value = service;
25                }
26
27                return service;
28            }
29
30            set => throw new NotSupportedException();
31        }
32    }
33}
Source Code
1using System;
2using System.Collections.Generic;
3
4namespace Doc.GlobalImportWithSetter
5{
6    internal class Foo
7    {
8        [Import]
9        private IFormatProvider? _formatProvider;


10    }
11

















12    internal 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 => _instance._services[typeof(T)] = service;
27    }
28}
Transformed Code
1using System;
2using System.Collections.Generic;
3
4namespace Doc.GlobalImportWithSetter
5{
6    internal 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                    this._formatProvider1 = service;
20                }
21
22                return service;
23            }
24
25            set
26            {
27                throw new NotSupportedException();
28            }
29        }
30    }
31
32    internal 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 => _instance._services[typeof(T)] = service;
47    }
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.Aspects;
2using Metalama.Framework.Code;
3using Microsoft.Win32;
4using System;
5using System.Linq;
6
7namespace Doc.RegistryStorage
8{
9    internal class RegistryStorageAttribute : TypeAspect
10    {
11        public string Key { get; }
12
13        public RegistryStorageAttribute( string key )
14        {
15            this.Key = "HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\" + key;
16        }
17
18        public override void BuildAspect( IAspectBuilder<INamedType> builder )
19        {
20            foreach ( var property in builder.Target.FieldsAndProperties.Where( p => !p.IsImplicitlyDeclared && p.IsAutoPropertyOrField == true ) )
21            {
22                builder.Advice.Override( property, nameof(this.OverrideProperty) );
23            }
24        }
25
26        [Template]
27        private dynamic? OverrideProperty
28        {
29            get
30            {
31                var value = Registry.GetValue( this.Key, meta.Target.FieldOrProperty.Name, null );
32
33                if ( value != null )
34                {
35                    return Convert.ChangeType( value, meta.Target.FieldOrProperty.Type.ToType() );
36                }
37                else
38                {
39                    return meta.Target.FieldOrProperty.Type.DefaultValue();
40                }
41            }
42
43            set
44            {
45                var stringValue = Convert.ToString( value );
46                Registry.SetValue( this.Key, meta.Target.FieldOrProperty.Name, stringValue );
47                meta.Proceed();
48            }
49        }
50    }
51}
Source Code
1namespace Doc.RegistryStorage
2{



3    [RegistryStorage( "Animals" )]
4    internal 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    }
12}
Transformed Code
1using System;
2using Microsoft.Win32;
3
4namespace Doc.RegistryStorage
5{
6    [RegistryStorage("Animals")]
7    internal class Animals
8    {
9
10
11        private int _turtles;
12        public int Turtles
13        {
14            get
15            {
16                var value = Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Turtles", null);
17                if (value != null)
18                {
19                    return (int)Convert.ChangeType(value, typeof(int));
20                }
21                else
22                {
23                    return default;
24                }
25            }
26
27            set
28            {
29                var stringValue = Convert.ToString(value);
30                Registry.SetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Turtles", stringValue);
31                this._turtles = value;
32            }
33        }
34
35
36        private int _cats;
37
38        public int Cats
39        {
40            get
41            {
42                var value = Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Cats", null);
43                if (value != null)
44                {
45                    return (int)Convert.ChangeType(value, typeof(int));
46                }
47                else
48                {
49                    return default;
50                }
51            }
52
53            set
54            {
55                var stringValue = Convert.ToString(value);
56                Registry.SetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Cats", stringValue);
57                this._cats = value;
58            }
59        }
60
61        public int All => this.Turtles + this.Cats;
62    }
63}

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.Aspects;
2using Metalama.Framework.Code;
3
4namespace Doc.Normalize
5{
6    internal class NormalizeAttribute : FieldOrPropertyAspect
7    {
8        public override void BuildAspect( IAspectBuilder<IFieldOrProperty> builder )
9        {
10            builder.Advice.Override( builder.Target, nameof(this.OverrideProperty) );
11        }
12
13        [Template]
14        private string OverrideProperty
15        {
16            set => meta.Target.FieldOrProperty.Value = value?.Trim().ToLowerInvariant();
17        }
18    }
19}
Source Code
1namespace Doc.Normalize
2{
3    internal class Foo
4    {
5        [Normalize]
6        public string? Property { get; set; }



7    }
8}
Transformed Code
1namespace Doc.Normalize
2{
3    internal class Foo
4    {
5
6
7        private string? _property;
8        [Normalize]
9        public string? Property
10        {
11            get
12            {
13                return this._property;
14            }
15
16            set
17            {
18                this._property = value?.Trim().ToLowerInvariant();
19            }
20        }
21    }
22}

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. (Not yet implemented in OverrideAccessors anyway.)

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(), where T is either dynamic or a type compatible with the target field or property.
  • The setter template must be of signature void Setter(T value), where the name value of the first parameter is mandatory.