MetalamaConceptual documentationCreating aspectsAdvising codeImplementing interfaces
Open sandboxFocusImprove this doc

Implementing interfaces

Certain aspects necessitate modifying the target type to implement a new interface. This can only be achieved by using the programmatic advising API.

Step 1. Call IAdviceFactory.ImplementInterface

Within your implementation of the BuildAspect method, invoke the ImplementInterface method.

Step 2. Add interface members to the aspect class

Incorporate all interface members into the aspect class and label them with the [InterfaceMember] custom attribute. It is not necessary for the aspect class to implement the introduced interface.

The following rules pertain to interface members:

  • The name and signature of all template interface members must precisely match those of the introduced interface.
  • The accessibility of introduced members is inconsequential. The aspect framework will generate public members unless the IsExplicit property is set to true. In this scenario, an explicit implementation is created.

Implementing an interface in an entirely dynamic manner, that is, when the aspect does not already recognize the interface, is currently unsupported.

Example: IDisposable

In the subsequent example, the aspect introduces the IDisposable interface. The implementation of the Dispose method disposes of all fields or properties that implement the IDisposable interface.

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System;
4using System.Linq;
5
6namespace Doc.Disposable
7{
8    internal class DisposableAttribute : TypeAspect
9    {
10        public override void BuildAspect( IAspectBuilder<INamedType> builder )
11        {
12            builder.Advice.ImplementInterface(
13                builder.Target,
14                typeof(IDisposable),
15                whenExists: OverrideStrategy.Ignore );
16        }
17
18        // Introduces a the `Dispose(bool)` protected method, which is NOT an interface member.
19        [Introduce( Name = "Dispose", IsVirtual = true, WhenExists = OverrideStrategy.Override )]
20        protected void DisposeImpl( bool disposing )
21        {
22            // Call the base method, if any.
23            meta.Proceed();
24
25            var disposableFields = meta.Target.Type.FieldsAndProperties
26                .Where( x => x.Type.Is( typeof(IDisposable) ) && x.IsAutoPropertyOrField == true );
27
28            // Disposes the current field or property.
29            foreach ( var field in disposableFields )
30            {
31                field.Value?.Dispose();
32            }
33        }
34
35        // Implementation of IDisposable.Dispose.
36        [InterfaceMember]
37        public void Dispose()
38        {
39            meta.This.Dispose( true );
40        }
41    }
42}
Source Code
1using System.IO;
2using System.Threading;

3
4#pragma warning disable CA1001 // Types that own disposable fields should be disposable
5
6namespace Doc.Disposable
7{
8    [Disposable]
9    internal class Foo
10    {
11        private CancellationTokenSource _cancellationTokenSource = new();
12    }
13




14    [Disposable]
15    internal class Bar : Foo






16    {
17        private MemoryStream _stream = new();
18    }
19}
Transformed Code
1using System;
2using System.IO;
3using System.Threading;
4
5#pragma warning disable CA1001 // Types that own disposable fields should be disposable
6
7namespace Doc.Disposable
8{
9    [Disposable]
10    internal class Foo : IDisposable
11    {
12        private CancellationTokenSource _cancellationTokenSource = new();
13
14        public void Dispose()
15        {
16            this.Dispose(true);
17        }
18
19        protected virtual void Dispose(bool disposing)
20        {
21            this._cancellationTokenSource.Dispose();
22        }
23    }
24
25    [Disposable]
26    internal class Bar : Foo
27    {
28        private MemoryStream _stream = new();
29
30        protected override void Dispose(bool disposing)
31        {
32            base.Dispose(disposing);
33            this._stream.Dispose();
34        }
35    }
36}

Example: Deep cloning

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System;
4using System.Linq;
5
6namespace Doc.DeepClone
7{
8    [Inheritable]
9    public class DeepCloneAttribute : TypeAspect
10    {
11        public override void BuildAspect( IAspectBuilder<INamedType> builder )
12        {
13            builder.Advice.IntroduceMethod(
14                builder.Target,
15                nameof(this.CloneImpl),
16                whenExists: OverrideStrategy.Override,
17                buildMethod: t =>
18                {
19                    t.Name = "Clone";
20                    t.ReturnType = builder.Target;
21                } );
22
23            builder.Advice.ImplementInterface(
24                builder.Target,
25                typeof(ICloneable),
26                whenExists: OverrideStrategy.Ignore );
27        }
28
29        [Template( IsVirtual = true )]
30        public virtual dynamic CloneImpl()
31        {
32            // This compile-time variable will receive the expression representing the base call.
33            // If we have a public Clone method, we will use it (this is the chaining pattern). Otherwise,
34            // we will call MemberwiseClone (this is the initialization of the pattern).
35            IExpression baseCall;
36
37            if ( meta.Target.Method.IsOverride )
38            {
39                baseCall = meta.Base.Clone();
40            }
41            else
42            {
43                baseCall = meta.Base.MemberwiseClone();
44            }
45
46            // Define a local variable of the same type as the target type.
47            var clone = meta.Cast( meta.Target.Type, baseCall )!;
48
49            // Select clonable fields.
50            var clonableFields =
51                meta.Target.Type.FieldsAndProperties.Where(
52                    f => f.IsAutoPropertyOrField == true &&
53                         ((f.Type.Is( typeof(ICloneable) ) && f.Type.SpecialType != SpecialType.String) ||
54                          (f.Type is INamedType { BelongsToCurrentProject: true } fieldNamedType &&
55                           fieldNamedType.Enhancements().HasAspect<DeepCloneAttribute>())) );
56
57            foreach ( var field in clonableFields )
58            {
59                // Check if we have a public method 'Clone()' for the type of the field.
60                var fieldType = (INamedType) field.Type;
61                var cloneMethod = fieldType.Methods.OfExactSignature( "Clone", Array.Empty<IType>() );
62
63                IExpression callClone;
64
65                if ( cloneMethod is { Accessibility: Accessibility.Public } ||
66                     fieldType.Enhancements().HasAspect<DeepCloneAttribute>() )
67                {
68                    // If yes, call the method without a cast.
69                    callClone = field.Value?.Clone()!;
70                }
71                else
72                {
73                    // If no, explicitly cast to the interface.
74                    callClone = (IExpression) ((ICloneable?) field.Value)?.Clone()!;
75                }
76
77                if ( cloneMethod == null || !cloneMethod.ReturnType.ToNullableType().Is( fieldType ) )
78                {
79                    // If necessary, cast the return value of Clone to the field type.
80                    callClone = (IExpression) meta.Cast( fieldType, callClone.Value );
81                }
82
83                // Finally, set the field value.
84                field.With( (IExpression) clone ).Value = callClone.Value;
85            }
86
87            return clone;
88        }
89
90        [InterfaceMember( IsExplicit = true )]
91        private object Clone()
92        {
93            return meta.This.Clone();
94        }
95    }
96}
Source Code
1using System;
2
3namespace Doc.DeepClone
4{
5    internal class ManuallyCloneable : ICloneable
6    {
7        public object Clone()
8        {
9            return new ManuallyCloneable();
10        }
11    }
12
13    [DeepClone]
14    internal class AutomaticallyCloneable
15    {
16        private int _a;
17        private ManuallyCloneable? _b;
18        private AutomaticallyCloneable? _c;
19    }
20







21    internal class DerivedCloneable : AutomaticallyCloneable
22    {






23        private string? _d;
24    }
25}
Transformed Code
1using System;
2
3namespace Doc.DeepClone
4{
5    internal class ManuallyCloneable : ICloneable
6    {
7        public object Clone()
8        {
9            return new ManuallyCloneable();
10        }
11    }
12
13    [DeepClone]
14    internal class AutomaticallyCloneable : ICloneable
15    {
16        private int _a;
17        private ManuallyCloneable? _b;
18        private AutomaticallyCloneable? _c;
19
20        public virtual AutomaticallyCloneable Clone()
21        {
22            var clone = ((AutomaticallyCloneable)this.MemberwiseClone())!;
23            clone._b = (ManuallyCloneable?)this._b?.Clone()!;
24            clone._c = this._c?.Clone()!;
25            return clone;
26        }
27
28        object ICloneable.Clone()
29        {
30            return Clone();
31        }
32    }
33
34    internal class DerivedCloneable : AutomaticallyCloneable
35    {
36        private string? _d;
37
38        public override DerivedCloneable Clone()
39        {
40            var clone = ((DerivedCloneable)base.Clone())!;
41            return clone;
42        }
43    }
44}

Referencing interface members in other templates

When introducing an interface member to the type, you 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 can be employed to access explicit implementations:

  • Cast the instance to the interface and access the member:

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