Metalama//Conceptual documentation/Creating aspects/Creating simple aspects/Overriding fields or properties
Open sandboxFocus

Getting started: overriding fields and properties

In Getting started: overriding a method, you have learned how to change the implementation of a method with an aspect. You can do the same with fields and properties by extending OverrideFieldOrPropertyAspect.

Overriding a field or property

Follow these steps to create an aspect that can override a field or property.

  1. Add Metalama the Metalama.Framework package to your project.

  2. Create a new class derived from the OverrideFieldOrPropertyAspect abstract class. This class will be a custom attribute, so naming it with the Attribute suffix is a good idea.

  3. Implement the OverrideProperty property in plain C#. To call the original implementation, call meta.Proceed.

  4. The aspect is a custom attribute. To transform a field or property using the aspect, just add the aspect custom attribute to the field or property.

Warning

When applying an aspect to a field, Metalama will automatically transform the field into a property. If the field is used by reference using ref, out, and in keywords, it will result in a compile-time error.

Trick: an empty OverrideFieldOrPropertyAspect aspect

The next example shows an empty implementation of OverrideFieldOrPropertyAspect applied to a property and to a field.

1using Metalama.Framework.Aspects;
2
3namespace Doc
4{
5    public class EmptyOverrideFieldOrPropertyAttribute : OverrideFieldOrPropertyAspect
6    {
7        public override dynamic? OverrideProperty
8        {
9            get => meta.Proceed();
10            set => meta.Proceed();
11        }
12    }
13}
Source Code
1namespace Doc
2{
3    internal class EmptyOverrideFieldOrPropertyExample
4    {
5        [EmptyOverrideFieldOrProperty]


6        public int Field;
7













8        [EmptyOverrideFieldOrProperty]
9        public string? Property { get; set; }
10    }











11}
Transformed Code
1namespace Doc
2{
3    internal class EmptyOverrideFieldOrPropertyExample
4    {
5        private int _field;
6
7        [EmptyOverrideFieldOrProperty]
8        public int Field
9        {
10            get
11            {
12                return _field;
13            }
14
15            set
16            {
17                this._field = value;
18            }
19        }
20
21        private string? _property;
22
23        [EmptyOverrideFieldOrProperty]
24        public string? Property
25        {
26            get
27            {
28                return _property;
29            }
30
31            set
32            {
33                this._property = value;
34            }
35        }
36    }
37}

This aspect does not do anything useful, but, as you can see, it transforms the field into a property.

Getting or setting the underlying property

If you have only worked with methods so far, you may already be used to using the meta.Proceed() method in your template. This method also works in a property template: when called from the getter, it returns the field or property value; when called from the setter, it sets the field or property to the value of the value parameter.

If you need to get the property value from the setter, or if you need to set the property value to something else than the value parameter, you can do it by getting or setting the meta.Target.FieldOrProperty.Value property.

Example: trimming strings

In this aspect, you shall see how you can trim whitespace before and after string values before they are assigned to the field or property.

1using Metalama.Framework.Aspects;
2
3namespace Doc.Trimmed
4{
5    public class TrimAttribute : OverrideFieldOrPropertyAspect
6    {
7        public override dynamic? OverrideProperty
8        {
9            get => meta.Proceed();
10            set => meta.Target.FieldOrProperty.Value = value?.Trim();
11        }
12    }
13}
14
Source Code
1using System;
2
3namespace Doc.Trimmed
4{
5    public class Details
6    {



7        [Trim]
8        public string? Code { get; set; }











9
10    }
11
12    public class Program
13    {
14        public static void Main(string[] args)
15        {
16            Details detail1 = new()
17            {
18                Code = "   GW12345  "
19            };
20
21            Console.WriteLine($"Code='{detail1.Code}'");
22
23        }
24    }
25}
26
Transformed Code
1using System;
2
3namespace Doc.Trimmed
4{
5    public class Details
6    {
7
8
9        private string? _code;
10        [Trim]
11        public string? Code
12        {
13            get
14            {
15                return _code;
16            }
17
18            set
19            {
20                this._code = value?.Trim();
21            }
22        }
23
24    }
25
26    public class Program
27    {
28        public static void Main(string[] args)
29        {
30            Details detail1 = new()
31            {
32                Code = "   GW12345  "
33            };
34
35            Console.WriteLine($"Code='{detail1.Code}'");
36
37        }
38    }
39}
40
Code 'GW12345' is valid : True
Code 'XY123455' is valid : False

The aspect does not need to modify the getter, so it only calls meta.Proceed() and Metalama replaces this call with the original implementation of the property. We could have written get => meta.Target.PropertyOrField.Value instead, with the same effect.

The setter is modified to call the Trim method on the input value. The shortest and simplest code is set => meta.Target.PropertyOrField.Value = value?.Trim. Alternatively, we could have written the following code.

set
{
    value = value?.Trim();
    meta.Proceed();
}

Example: turning the value to upper case

The following example is similar to the previous one: instead of trimming a string, we normalize it to upper case.

We apply the aspect to a class representing a shipment between two airports.

1using Metalama.Framework.Aspects;
2
3
4namespace Doc.UpperCase
5{
6    /// <summary>
7    /// Changes the value of a string property to Upper Case
8    /// </summary>
9    public class UpperCaseAttribute : OverrideFieldOrPropertyAspect
10    {
11        public override dynamic? OverrideProperty
12        {
13            get => meta.Proceed();
14            set => meta.Target.FieldOrProperty.Value = value?.ToUpper();
15        }
16    }
17}
18
Source Code
1using System;
2
3
4namespace Doc.UpperCase
5{
6    public class Shipment
7    {


8        [UpperCase]
9        public string? From;













10
11        [UpperCase]
12        public string? To { get; set; }











13    }
14
15    public class UpperCase
16    {
17        public static void Main(string[] args)
18        {
19            var package = new Shipment();
20            package.From = "lhr";
21            package.To = "jfk";
22
23            Console.WriteLine($"Package is booked from {package.From} to {package.To}");
24        }
25    }
26}
27
Transformed Code
1using System;
2
3
4namespace Doc.UpperCase
5{
6    public class Shipment
7    {
8        private string? _from;
9
10        [UpperCase]
11        public string? From
12        {
13            get
14            {
15                return _from;
16            }
17
18            set
19            {
20                this._from = value?.ToUpper();
21            }
22        }
23
24        private string? _to;
25
26        [UpperCase]
27        public string? To
28        {
29            get
30            {
31                return _to;
32            }
33
34            set
35            {
36                this._to = value?.ToUpper();
37            }
38        }
39    }
40
41    public class UpperCase
42    {
43        public static void Main(string[] args)
44        {
45            var package = new Shipment();
46            package.From = "lhr";
47            package.To = "jfk";
48
49            Console.WriteLine($"Package is booked from {package.From} to {package.To}");
50        }
51    }
52}
53
Package is booked from LHR to JFK

Note that, in this example, From is a public field and To is a public property. They are deliberately kept that way to show that the aspect actually works on both because IFieldOrProperty is used in the aspect. If you want to aspect to be applicable only on properties and not on fields you have to use IProperty

Going deeper

If you want to go deeper with field/property overrides, consider jumping to the following articles:

  • In this article, you have seen how to use meta.Proceed and meta.Target.Method.Name in your templates. You can write much more complex and powerful templates, even doing compile-time if and foreach blocks. To see how, you can jump directly to Writing T# templates.

  • To learn how to override several fields and properties from a single type-level aspect, jump to Overriding methods.