Project "Caravela" 0.3 / / Caravela Documentation / Conceptual Documentation / Creating Aspects / Advising Code / Overriding Members: Reloaded

Overriding Members (Reloaded)

In the section Creating Simple Aspects, you have learned to override methods, properties, fields and events using a simple object-oriented API. In this section, you will learn how to achieve the same thing using the advising API. This will allow you to modify not only the method that is the immediate target of the aspect, but any method in the type being targeted.

Note

In this article, we will assume you have learned the techniques explained in Creating Simple Aspects.

Overriding methods

To override one or more methods, your aspect needs to implement the BuildAspect method and invoke builder.Advices.OverrideMethod method.

The first argument of OverrideMethod is the IMethod that you want to override. This method must be in the type being targeted by the current aspect instance.

The second argument of OverrideMethod 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 must have 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 match any parameter list) and have the universal dynamic return type, which also matches void.

    dynamic? Template()
    

Example: synchronized object

The following aspects 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 System;
using System.Linq;
using Caravela.Framework.Aspects;
using Caravela.Framework.Code;

namespace Caravela.Documentation.SampleCode.AspectFramework.Synchronized
{
    class SynchronizedAttribute : TypeAspect
    {
        public override void BuildAspect( IAspectBuilder<INamedType> builder )
        {
            foreach ( var method in builder.Target.Methods.Where( m => !m.IsStatic))
            {
                builder.Advices.OverrideMethod(method, nameof(OverrideMethod));
            }
        }

        [Template]
        private dynamic? OverrideMethod()
        {
            lock ( meta.This )
            {
                return meta.Proceed();
            }
        }
    }
}
namespace Caravela.Documentation.SampleCode.AspectFramework.Synchronized
{
    [Synchronized]
    class SynchronizedClass
    {
        double _total;
        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 Caravela.Documentation.SampleCode.AspectFramework.Synchronized
{
    [Synchronized]
    class SynchronizedClass
    {
        double _total;
        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 provide several of them and let the framework choose which one is the most suitable. The principle of this feature is described in Overriding Methods. 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 OverrideMethod and MethodTemplateSelector for details.

Overriding fields or properties

Overriding a field or a property means overriding its get and/or set semantic. From the point of view of overriding, fields are transformed into properties of the same name and accessibility.

Before applying the templates, the aspect framework transforms fields and automatic properties into field-backed properties.

Warning

When you override a field, it is no longer possible to reference the field using the out, ref or in keywords. Such cases are currently unsupported and will result in compilation errors. The workaround is to use an intermediate local variable.

There are two approaches to override a field or property: by providing a property template, or by providing one or more accessor templates.

Overriding with a property template

This approach is the simplest but it has a few limitations.

Just like for methods, to override one or more fields or properties, your aspect needs to implement the BuildAspect method exposed on builder.Advices.

The first argument of OverrideFieldOrProperty is the IFieldOrProperty that you want to override. This field or property must be in the type being targeted by the current aspect instance.

The second argument of OverrideFieldOrProperty 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?, or a type that is compatible with the type of the overridden property.

Example: registry-backed class

The following aspect overrides properties so that they are written to and read from the Windows registry.

using System;
using System.Linq;
using Caravela.Framework.Aspects;
using Caravela.Framework.Code;
using Microsoft.Win32;

namespace Caravela.Documentation.SampleCode.AspectFramework.RegistryStorage
{
    class RegistryStorageAttribute : TypeAspect
    {
        public string Key { get; }

        public RegistryStorageAttribute(string key)
        {
            this.Key = "HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\" + key;
        }

        public override void BuildAspect(IAspectBuilder<INamedType> builder )
        {
            foreach ( var property in builder.Target.FieldsAndProperties.Where( p=> p.IsAutoPropertyOrField))
            {
                builder.Advices.OverrideFieldOrProperty( property, nameof(this.OverrideProperty));
            }
            
        }

        [Template]
        dynamic? OverrideProperty
        {
            get
            {
                var type = meta.Target.FieldOrProperty.Type.ToType();
                var value = Registry.GetValue(this.Key, meta.Target.FieldOrProperty.Name, null);
                if (value != null)
                {
                    return Convert.ChangeType(value, type);
                }
                else
                {
                    return meta.Target.FieldOrProperty.Type.DefaultValue();
                }
            }

            set
            {
                var stringValue = Convert.ToString(value);
                Registry.SetValue(this.Key, meta.Target.FieldOrProperty.Name, stringValue);
                meta.Proceed();
            }
        }
    }
}
namespace Caravela.Documentation.SampleCode.AspectFramework.RegistryStorage
{
    [RegistryStorage("Animals")]
    class Animals
    {
        public int Turtles { get; set; }

        public int Cats { get; set; }

        public int All => this.Turtles + this.Cats;
    }
}
using System;
using Microsoft.Win32;

namespace Caravela.Documentation.SampleCode.AspectFramework.RegistryStorage
{
    [RegistryStorage("Animals")]
    class Animals
    {


        private int _turtles;
        public int Turtles
        {
            get
            {
                var value = Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Turtles", null);
                if (value != null)
                {
                    return (int)Convert.ChangeType(value, typeof(int));
                }
                else
                {
                    return default;
                }
            }

            set
            {
                var stringValue = Convert.ToString(value);
                Registry.SetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Turtles", stringValue);
                this._turtles = value;
            }
        }


        private int _cats;

        public int Cats
        {
            get
            {
                var value = Registry.GetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Cats", null);
                if (value != null)
                {
                    return (int)Convert.ChangeType(value, typeof(int));
                }
                else
                {
                    return default;
                }
            }

            set
            {
                var stringValue = Convert.ToString(value);
                Registry.SetValue("HKEY_CURRENT_USER\\SOFTWARE\\Company\\Product\\Animals", "Cats", stringValue);
                this._cats = value;
            }
        }

        public int All => this.Turtles + this.Cats;
    }
}

Strongly-typed templates

Instead of providing a dynamically typed template, you can provide a template whose type is compatible with the type of the overridden property.

Overriding only one accessor

The 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.

Getting or setting the property value

If you have only worked with methods so far, you may be already used to use 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: 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 of properties of type string. It overrides the setter to trim and lower case the assigned value.

using System;
using Caravela.Framework.Aspects;
using Caravela.Framework.Code;

namespace Caravela.Documentation.SampleCode.AspectFramework.Normalize
{
    class NormalizeAttribute : FieldOrPropertyAspect
    {
        public override void BuildAspect( IAspectBuilder<IFieldOrProperty> builder )
        {
            builder.Advices.OverrideFieldOrProperty(builder.Target, nameof(this.OverrideProperty));
        }

        [Template]
        string OverrideProperty
        {
            set => meta.Target.FieldOrProperty.Value = value?.Trim().ToLowerInvariant();
        }
    }
}
namespace Caravela.Documentation.SampleCode.AspectFramework.Normalize
{
    class TargetCode
    {
        [Normalize]
        public string? Property { get; set; }
    }
}
namespace Caravela.Documentation.SampleCode.AspectFramework.Normalize
{
    class TargetCode
    {


        private string? _property;
        [Normalize]
        public string? Property
        {
            get
            {
                return this.Property_Source;
            }

            set
            {
                this.Property_Source = value?.Trim().ToLowerInvariant();
            }
        }

        private string? Property_Source
        {
            get
            {
                return this._property;
            }

            set
            {
                this._property = value;
            }
        }
    }
}

Overriding with accessor templates

Advising fields or properties with the OverrideFieldOrProperty has the following limitations over the use of OverrideFieldOrPropertyAccessors:

  • You cannot choose a template for each accessor separately.
  • You cannot have generic templates. (Not yet implemented in OverrideFieldOrPropertyAccessors anyway.)

To alleviate these limitations, you can use the method OverrideFieldOrPropertyAccessors 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.

Overriding events

Overriding events is possible using the OverrideEventAccessors method. It follows the same principles than OverridePropertyAccessors.

It is possible to override the add and remove semantics of an event, but not yet the invocation of an event. Therefore, it is of little use and we are skipping the example.