Open sandboxFocusImprove this doc

Advising a single type with a fabric

Instead of using aspects, you can advise the current type using a type fabric. A type fabric is a compile-time nested class that functions as a type-level aspect added to the target type.

Setting up a type fabric

To advise a type using a type fabric, follow these steps:

  1. Create a nested type derived from the TypeFabric class.

    Note

    For optimal design-time performance and usability, we recommend implementing type fabrics in a separate file and marking the containing type as partial.

  2. Override the AmendType method.

  3. Call advising methods directly on the amender parameter. ITypeAmender implements IAdviser<T>, so you can use all extension methods from AdviserExtensions directly, such as IntroduceMethod, Override, or ImplementInterface. To use this feature, you must be familiar with advanced aspects. For more details, refer to Transforming code.

    To advise a member of the target type instead of the type itself, use the With method to obtain an adviser for that member.

  4. Optionally, you can add declarative advice, such as member introductions, to your type fabrics. For more information, see Introducing members.

Note

Type fabrics are always executed first, before any aspect. As a result, they can only add advice to members defined in the source code. If you need to add advice to members introduced by an aspect, you'll need to use a helper aspect and order it after the aspects that provide the members you wish to advise.

Example: introducing members

The following example demonstrates how to create a type fabric that introduces ten methods to the target type.

Source Code
1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Fabrics;
4
5namespace Doc.AdvisingTypeFabric;
6
7internal class MyClass
8{



9    private class Fabric : TypeFabric
10    {



11        [Template]
12        public int MethodTemplate( [CompileTime] int index )
13        {
14            return index;
15        }



































16
17        public override void AmendType( ITypeAmender amender )
18        {
19            for ( var i = 0; i < 10; i++ )
20            {
21                amender.IntroduceMethod(
22                    nameof(this.MethodTemplate),



23                    args: new { index = i },
24                    buildMethod: m => m.Name = "Method" + i );
25            }
26        }
27    }




28}
Transformed Code
1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Fabrics;
4
5namespace Doc.AdvisingTypeFabric;
6
7
8#pragma warning disable CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
9
10internal class MyClass
11{
12
13#pragma warning disable CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
14
15    private class Fabric : TypeFabric
16    {
17        [Template]
18        public int MethodTemplate([CompileTime] int index) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
19
20
21        public override void AmendType(ITypeAmender amender) => throw new System.NotSupportedException("Compile-time-only code cannot be called at run-time.");
22    }
23
24#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
25
26
27
28    public int Method0()
29    {
30        return 0;
31    }
32
33    public int Method1()
34    {
35        return 1;
36    }
37
38    public int Method2()
39    {
40        return 2;
41    }
42
43    public int Method3()
44    {
45        return 3;
46    }
47
48    public int Method4()
49    {
50        return 4;
51    }
52
53    public int Method5()
54    {
55        return 5;
56    }
57
58    public int Method6()
59    {
60        return 6;
61    }
62
63    public int Method7()
64    {
65        return 7;
66    }
67



68    public int Method8()
69    {
70        return 8;
71    }
72
73    public int Method9()
74    {
75        return 9;
76    }
77}
78
79#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
80
81

Reporting diagnostics

The ITypeAmender exposes a Diagnostics property of type ScopedDiagnosticSink. You can use it to report or suppress diagnostics scoped to the target type, just as you would in an aspect's BuildAspect method.

For details on how to define and report diagnostics, see Reporting and suppressing diagnostics.

Example: overriding a method and reporting a diagnostic

The following example shows how to use a type fabric to override a method and report a diagnostic when the target type is missing the expected Name property. Notice how advice extension methods are called directly on the amender and the With method is used to advise a specific method.

1using Metalama.Framework.Advising;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using Metalama.Framework.Diagnostics;
5using Metalama.Framework.Fabrics;
6using System.Linq;
7
8namespace Doc.AdvisingTypeFabricDiagnostics;
9
10public partial class MyClass
11{
12    private class Fabric : TypeFabric
13    {
14        private static readonly DiagnosticDefinition<INamedType> _warning = new(
15            "MY001",
16            Severity.Warning,
17            "The type '{0}' should have a 'Name' property." );
18
19        [Template]
20        public string ToStringTemplate()
21        {
22            return $"{meta.Target.Type.Name} (advised by fabric)";
23        }
24
25        public override void AmendType( ITypeAmender amender )
26        {
27            // Report a warning if the type does not have a 'Name' property.
28            if ( !amender.Type.Properties.OfName( "Name" ).Any() )
29            {
30                amender.Diagnostics.Report( _warning.WithArguments( amender.Type ) );
31            }
32
33            // Override ToString directly on the amender using the With method.
34            amender.With( amender.Type.Methods.OfName( "ToString" ).Single() )
35                .Override( nameof(this.ToStringTemplate) );
36        }
37    }
38}
39
Source Code
1namespace Doc.AdvisingTypeFabricDiagnostics;
2
Warning MY001: The type 'MyClass' should have a 'Name' property.

3public partial class MyClass
4{



5    public override string ToString() => "MyClass";
6}


7
Transformed Code
1namespace Doc.AdvisingTypeFabricDiagnostics;
2
3
4#pragma warning disable CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
5
6public partial class MyClass
7{
8    public override string ToString()
9    {
10        return "MyClass (advised by fabric)";
11    }
12}
13
14#pragma warning restore CS0067, CS8618, CS0162, CS0169, CS0414, CA1822, CA1823, IDE0051, IDE0052
15
16
17