Metalama 1.0 / / Metalama Documentation / Conceptual Documentation / Creating Aspects / Transforming Code / Implementing Interfaces

Implementing Interfaces

Many aspects need to modify the target type so it implements a new interface. This can be done only using the programmatic advising API.

Step 1. Call IAdviceFactory.ImplementInterface

In your implementation of the BuildAspect method, call the ImplementInterface method.

Step 2. Add interface members to the aspect class

Add all interface members to the aspect class and mark them with the [InterfaceMember] custom attribute. There is no need to have the aspect class implement the introduced interface.

The following rules apply to interface members:

  • The name and signature of all interface members must exactly match.
  • The accessibility of introduced members have no importance.
  • The aspect framework will generate public members unless the IsExplicit property is set to true. In this case, an explicit implementation is generated.

Implementing an interface in a complete dynamic manner, when the interface itself is not known by the aspect, is not yet supported.

Example: IDisposable

The aspect in the next example introduces the IDisposable interface. The implementation of the Dispose method disposes of all fields or properties that implement the IDisposable interface.

using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using System;
using System.Linq;

namespace Doc.Disposable
{
    internal class DisposableAttribute : TypeAspect
    {
        public override void BuildAspect( IAspectBuilder<INamedType> builder )
        {
            builder.Advice.ImplementInterface(
                builder.Target,
                typeof(IDisposable),
                whenExists: OverrideStrategy.Ignore );
        }

        // Introduces a the `Dispose(bool)` protected method, which is NOT an interface member.
        [Introduce( Name = "Dispose", IsVirtual = true, WhenExists = OverrideStrategy.Override )]
        protected void DisposeImpl( bool disposing )
        {
            // Call the base method, if any.
            meta.Proceed();

            var disposableFields = meta.Target.Type.FieldsAndProperties
                .Where( x => x.Type.Is( typeof(IDisposable) ) && x.IsAutoPropertyOrField );

            // Disposes the current field or property.
            foreach ( var field in disposableFields )
            {
                field.Invokers.Final.GetValue( meta.This )?.Dispose();
            }
        }

        // Implementation of IDisposable.Dispose.
        [InterfaceMember]
        public void Dispose()
        {
            meta.This.Dispose( true );
        }
    }
}
using System.IO;
using System.Threading;

namespace Doc.Disposable
{
    [Disposable]
    internal class Foo
    {
        private CancellationTokenSource _cancellationTokenSource = new();
    }

    [Disposable]
    internal class Bar : Foo
    {
        private FileSystemWatcher _cancellationTokenSource = new();
    }
}
using System;
using System.IO;
using System.Threading;

namespace Doc.Disposable
{
    [Disposable]
    internal class Foo : IDisposable
    {
        private CancellationTokenSource _cancellationTokenSource = new();

        public void Dispose()
        {
            this.Dispose(true);
        }

        protected virtual void Dispose(bool disposing)
        {
            this._cancellationTokenSource?.Dispose();
        }

        private void Dispose_Source(bool disposing)
        {
        }
    }

    [Disposable]
    internal class Bar : Foo
    {
        private FileSystemWatcher _cancellationTokenSource = new();

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            this._cancellationTokenSource?.Dispose();
        }
    }
}

Example: deep cloning

using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using Metalama.Framework.Code.SyntaxBuilders;
using System;
using System.Linq;

namespace Doc.DeepClone
{
    [Inherited]
    public class DeepCloneAttribute : TypeAspect
    {
        public override void BuildAspect( IAspectBuilder<INamedType> builder )
        {
            builder.Advice.IntroduceMethod(
                builder.Target,
                nameof(this.CloneImpl),
                whenExists: OverrideStrategy.Override,
                buildMethod: t =>
                {
                    t.Name = "Clone";
                    t.ReturnType = builder.Target;
                } );

            builder.Advice.ImplementInterface(
                builder.Target,
                typeof(ICloneable),
                whenExists: OverrideStrategy.Ignore );
        }

        [Template( IsVirtual = true )]
        public virtual dynamic CloneImpl()
        {
            // This compile-time variable will receive the expression representing the base call.
            // If we have a public Clone method, we will use it (this is the chaining pattern). Otherwise,
            // we will call MemberwiseClone (this is the initialization of the pattern).
            IExpression baseCall;

            if ( meta.Target.Method.IsOverride )
            {
                ExpressionFactory.Capture( meta.Base.Clone(), out baseCall );
            }
            else
            {
                ExpressionFactory.Capture( meta.Base.MemberwiseClone(), out baseCall );
            }

            // Define a local variable of the same type as the target type.
            var clone = meta.Cast( meta.Target.Type, baseCall )!;

            // Select clonable fields.
            var clonableFields =
                meta.Target.Type.FieldsAndProperties.Where(
                    f => f.IsAutoPropertyOrField &&
                         ((f.Type.Is( typeof(ICloneable) ) && f.Type.SpecialType != SpecialType.String) ||
                          (f.Type is INamedType fieldNamedType &&
                           fieldNamedType.Aspects<DeepCloneAttribute>().Any())) );

            foreach ( var field in clonableFields )
            {
                // Check if we have a public method 'Clone()' for the type of the field.
                var fieldType = (INamedType) field.Type;
                var cloneMethod = fieldType.Methods.OfExactSignature( "Clone", Array.Empty<IType>() );

                IExpression callClone;

                if ( cloneMethod is { Accessibility: Accessibility.Public } ||
                     fieldType.Aspects<DeepCloneAttribute>().Any() )
                {
                    // If yes, call the method without a cast.
                    ExpressionFactory.Capture( field.Invokers.Base!.GetValue( meta.This )?.Clone(), out callClone );
                }
                else
                {
                    // If no, explicitly cast to the interface.
                    ExpressionFactory.Capture( ((ICloneable) field.Invokers.Base!.GetValue( meta.This ))?.Clone(), out callClone );
                }

                if ( cloneMethod == null || !cloneMethod.ReturnType.ConstructNullable().Is( fieldType ) )
                {
                    // If necessary, cast the return value of Clone to the field type.
                    ExpressionFactory.Capture( meta.Cast( fieldType, callClone.Value )!, out callClone );
                }

                // Finally, set the field value.
                field.Invokers.Base!.SetValue( clone, callClone.Value );
            }

            return clone;
        }

        [InterfaceMember( IsExplicit = true )]
        private object Clone()
        {
            return meta.This.Clone();
        }
    }
}
using System;

namespace Doc.DeepClone
{
    internal class ManuallyCloneable : ICloneable
    {
        public object Clone()
        {
            return new ManuallyCloneable();
        }
    }

    [DeepClone]
    internal class AutomaticallyCloneable
    {
        private int _a;
        private ManuallyCloneable? _b;
        private AutomaticallyCloneable? _c;
    }

    internal class DerivedCloneable : AutomaticallyCloneable
    {
        private string? _d;
    }
}
using System;

namespace Doc.DeepClone
{
    internal class ManuallyCloneable : ICloneable
    {
        public object Clone()
        {
            return new ManuallyCloneable();
        }
    }

    [DeepClone]
    internal class AutomaticallyCloneable : ICloneable
    {
        private int _a;
        private ManuallyCloneable? _b;
        private AutomaticallyCloneable? _c;

        public virtual AutomaticallyCloneable Clone()
        {
            var clone = (AutomaticallyCloneable)MemberwiseClone();
            clone._b = (ManuallyCloneable?)this._b?.Clone();
            clone._c = (this._c?.Clone());
            return clone;
        }

        private AutomaticallyCloneable Clone_Source()
        {
            return default(AutomaticallyCloneable);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }
    }

    internal class DerivedCloneable : AutomaticallyCloneable
    {
        private string? _d;

        public override DerivedCloneable Clone()
        {
            var clone = (DerivedCloneable)base.Clone();
            return clone;
        }
    }
}

Referencing interface members in other templates

When you introduce an interface member to a type, your will often want to access it from templates. Unless the member is an explicit implementation, you have two options:

Option 1. Access the aspect template member

            this.Dispose();

          

Option 2. Use meta.This and write dynamic code

            meta.This.Dispose();

          

Accessing explicit implementations

The following strategies are possible:

  • cast the instance to the interface and access the member, e.g.

    ((IDisposable)meta.This).Dispose();
    
  • introduce a private method with the real method implementation, and call this private member both from the interface member and from other templates.