Open sandboxFocusImprove this doc

Consuming code metrics

Code metrics provide quantitative measures of your source code, such as statement counts or syntax node counts. You can consume metrics in two scenarios:

  • Aspects and fabrics: Make decisions based on code complexity at compile time.
  • Workspaces API: Analyze code complexity from standalone applications or tools.

Built-in metrics

The Metalama.Extensions.Metrics package provides three ready-to-use metrics:

Metric Description
StatementsCount Counts statements in a declaration. More relevant than line counts, but less accurate than syntax nodes for modern expression-oriented C#.
SyntaxNodesCount Counts all syntax nodes in a declaration's syntax tree. Provides a more accurate measure of code complexity.
LinesOfCode Counts lines of code with three sub-metrics: Logical (excludes braces and comments), NonBlank (non-whitespace content), and Total (total line span).

You can apply all metrics to methods, constructors, types, namespaces, and the entire compilation. When applied to a container (type, namespace, compilation), the metric aggregates values from all contained members.

Using metrics in aspects and fabrics

Step 1. Add the NuGet package

Add the Metalama.Extensions.Metrics package to your project:

<PackageReference Include="Metalama.Extensions.Metrics" Version="$(MetalamaVersion)" />

Step 2. Get the metric value

Call the Metrics extension method on any declaration, then call Get with the metric type:

var syntaxNodeCount = method.Metrics().Get<SyntaxNodesCount>();
int count = syntaxNodeCount.Value;

Use this in:

  • Aspects: In BuildAspect to conditionally add advice, or in templates to embed metric values in generated code.
  • Fabrics: In AmendProject or AmendType to filter declarations based on complexity.

Example: adding logging to complex methods

The following example uses a fabric to add logging only to methods with more than 50 syntax nodes. The Add method is simple and remains unchanged, while the Fibonacci method exceeds the threshold and gets logging injected.

1using Metalama.Extensions.Metrics;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Metrics;
4using System;
5
6namespace Doc.LogComplexMethods;
7
8public class LogAttribute : OverrideMethodAspect
9{
10    public override dynamic? OverrideMethod()
11    {
12        // Get the syntax node count at compile time directly from the template.
13        var syntaxNodeCount = meta.Target.Method.Metrics().Get<SyntaxNodesCount>().Value;
14
15        Console.WriteLine( $"Entering {meta.Target.Method.Name} (complexity: {syntaxNodeCount} syntax nodes)" );
16
17        try
18        {
19            return meta.Proceed();
20        }
21        finally
22        {
23            Console.WriteLine( $"Leaving {meta.Target.Method.Name}" );
24        }
25    }
26}
27
1using Metalama.Extensions.Metrics;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Fabrics;
4using Metalama.Framework.Metrics;
5
6namespace Doc.LogComplexMethods;
7
8public class Fabric : ProjectFabric
9{
10    public override void AmendProject( IProjectAmender amender )
11    {
12        // Add logging to methods with more than 50 syntax nodes.
13        amender
14            .SelectMany( p => p.Types )
15            .SelectMany( t => t.Methods )
16            .Where( m => m.Metrics().Get<SyntaxNodesCount>().Value > 50 )
17            .AddAspectIfEligible<LogAttribute>();
18    }
19}
20
Source Code
1namespace Doc.LogComplexMethods;
2


3internal class Calculator
4{
5    // Simple method: ~15 syntax nodes - will NOT be logged.
6    public int Add( int a, int b )
7    {
8        return a + b;
9    }
10
11    // Complex method: ~65 syntax nodes - will be logged.
12    public int Fibonacci( int n )
13    {
14        if ( n <= 0 )
15        {



16            return 0;
17        }
18
19        if ( n == 1 )
20        {
21            return 1;
22        }
23
24        var prev = 0;
25        var current = 1;
26
27        for ( var i = 2; i <= n; i++ )
28        {
29            var next = prev + current;
30            prev = current;
31            current = next;
32        }
33
34        return current;
35    }
36}





37
Transformed Code
1using System;
2
3namespace Doc.LogComplexMethods;
4
5internal class Calculator
6{
7    // Simple method: ~15 syntax nodes - will NOT be logged.
8    public int Add(int a, int b)
9    {
10        return a + b;
11    }
12
13    // Complex method: ~65 syntax nodes - will be logged.
14    public int Fibonacci(int n)
15    {
16        Console.WriteLine("Entering Fibonacci (complexity: 62 syntax nodes)");
17        try
18        {
19            if (n <= 0)
20            {
21                return 0;
22            }
23
24            if (n == 1)
25            {
26                return 1;
27            }
28
29            var prev = 0;
30            var current = 1;
31
32            for (var i = 2; i <= n; i++)
33            {
34                var next = prev + current;
35                prev = current;
36                current = next;
37            }
38
39            return current;
40        }
41        finally
42        {
43            Console.WriteLine("Leaving Fibonacci");
44        }
45    }
46}
47

Using metrics with the Workspaces API

When using the Metalama.Framework.Workspaces API from a standalone application, you need to register the metric providers manually before loading a workspace.

Step 1. Add NuGet packages

Add both the Metalama.Framework.Workspaces and Metalama.Extensions.Metrics packages:

<PackageReference Include="Metalama.Framework.Workspaces" Version="$(MetalamaVersion)" />
<PackageReference Include="Metalama.Extensions.Metrics" Version="$(MetalamaVersion)" />

Step 2. Register metric providers

Before loading the workspace, call the AddMetrics extension method on ServiceBuilder:

// Register metric providers before loading the workspace.
WorkspaceCollection.Default.ServiceBuilder.AddMetrics();

Step 3. Load the workspace and query metrics

After registering the providers, load your workspace and query metrics as usual:

// Query methods and their metrics.
var complexMethods = workspace.SourceCode.Types
    .SelectMany( t => t.Methods )
    .Where( m => !m.IsAbstract )
    .Select( m => new
    {
        Method = m,
        SyntaxNodes = m.Metrics().Get<SyntaxNodesCount>().Value,
        Statements = m.Metrics().Get<StatementsCount>().Value
    } )
    .Where( m => m.SyntaxNodes > 20 )
    .OrderByDescending( m => m.SyntaxNodes );

Example: code complexity analyzer

The following example demonstrates a standalone tool that analyzes code complexity:

// This is public domain Metalama sample code.

using Metalama.Extensions.Metrics;
using Metalama.Framework.Metrics;
using Metalama.Framework.Workspaces;

if ( args.Length != 1 )
{
    Console.Error.WriteLine( "Usage: MetricsAnalyzer <csproj>" );

    return 1;
}

#region RegisterMetricProviders
// Register metric providers before loading the workspace.
WorkspaceCollection.Default.ServiceBuilder.AddMetrics();
#endregion

#region LoadWorkspace
// Load the workspace.
var workspace = WorkspaceCollection.Default.Load( args[0] );
#endregion

#region QueryMetrics
// Query methods and their metrics.
var complexMethods = workspace.SourceCode.Types
    .SelectMany( t => t.Methods )
    .Where( m => !m.IsAbstract )
    .Select( m => new
    {
        Method = m,
        SyntaxNodes = m.Metrics().Get<SyntaxNodesCount>().Value,
        Statements = m.Metrics().Get<StatementsCount>().Value
    } )
    .Where( m => m.SyntaxNodes > 20 )
    .OrderByDescending( m => m.SyntaxNodes );
#endregion

Console.WriteLine( "Complex methods (> 20 syntax nodes):" );
Console.WriteLine();

foreach ( var item in complexMethods )
{
    Console.WriteLine(
        $"  {item.Method.DeclaringType.Name}.{item.Method.Name}: " +
        $"{item.SyntaxNodes} nodes, {item.Statements} statements" );
}

return 0;

See also