Last Updated on October 13, 2026 by Aram
Most .NET applications have code that repeats for reasons unrelated to their business logic.
A method calculates an order total, but it also writes logs. Another method retrieves customer details, but it also measures execution time. Several classes validate arguments, implement property notifications, or enforce the same rules.
Each piece is useful. The problem appears when the same implementation spreads across dozens of methods.
Changing a logging convention becomes a search across the solution. A missing validation check creates inconsistent behavior. Business methods grow longer, making their actual purpose harder to see.
I recall in one project, I was trying to measure execution time across so many functions that include external API calls and database calls, which were part of a complex business logic.
During that time, I had to duplicate Stopwatch start and restart with a Log statement after each component, which was a time-consuming and tedious work.
Aspect-Oriented Programming gives us a way to organize these recurring concerns and apply them consistently. Metalama brings that approach to C# through compile-time code generation, with tooling that helps us understand the resulting code.
Why Aspect-Oriented Programming?
Logging, tracing, validation, and similar behaviors are often called cross-cutting concerns because they affect multiple parts of an application.
Consider a simple service:
public decimal CalculateTotal(decimal price, int quantity)
{
Console.WriteLine("Starting calculation");
try
{
return price * quantity;
}
finally
{
Console.WriteLine("Calculation completed");
}
}
The business operation is one expression. Everything around it implements a logging convention.
If twenty methods need the same behavior, copying this structure creates twenty places to maintain it.
With Aspect-Oriented Programming, we define the behavior in an aspect, then apply that aspect to selected declarations:
[Log] public decimal CalculateTotal(decimal price, int quantity) => price * quantity;
The aspect supplies the surrounding behavior, while the method remains focused on its responsibility.
This complements familiar approaches such as middleware and decorators. Those approaches remain useful at request boundaries and service boundaries; aspects offer another way to apply recurring behavior directly to methods, properties, parameters, and types.
More use cases include security, observability, change tracking, pulling dependencies, exception handling, design patterns such as builder, memento, factory, and many more use cases and applications.
You can read more about all use cases of Aspect-Oriented Programming in this page.
Introducing Metalama
Metalama is an open-source patterns and architecture toolkit for C#.
It helps developers define reusable patterns, generate repetitive implementation code, and validate declarations. Its capabilities extend beyond wrapping method calls. Official documentation: Metalama overview.
PostSharp Technologies is the company behind both PostSharp and Metalama, bringing its experience with .NET metaprogramming to these tools. It has been a leader in introducing the Aspect Oriented Programming to the world of .NET since 2004 through PostSharp
PostSharp deserves its own discussion, which we will explore in a separate article.
What does Metalama offer?
The practical benefits include:
- Less repetition: maintain a recurring implementation in one aspect.
- Consistent behavior: apply the same pattern across selected declarations.
- Focused business code: keep infrastructure details out of method bodies.
- Code generation: introduce members instead of maintaining boilerplate manually.
- Visibility: inspect transformations and debug the generated implementation.
Metalama normally transforms code during compilation without rewriting the source files you maintain. Live templates are a separate feature that deliberately changes source code. Check Getting started.
Where Metaprogramming Fits
Metaprogramming means writing code that works with other code: inspecting it, generating it, or transforming it.
Metalama uses this technique to implement aspects. Its templates combine compile-time decisions with instructions for generating runtime C#.
The template language is called T#. It uses C#-compatible syntax, but the template executes during compilation to produce the application code that runs later. For a further read about T# templates, check the Official documentation: T# templates from Metalama.
Aspects, Reflection, and Runtime Performance
Aspect-Oriented Programming and reflection describe different things.
Aspect-Oriented Programming organizes cross-cutting behavior. Reflection lets a running application inspect metadata and, when needed, access members dynamically. An AOP implementation can use reflection, but reflection is not required.
Metalama inspects its code model during compilation and generates the implementation ahead of execution. A logging aspect produces ordinary logging statements around the original method body. Calls do not need to pass through a reflection-based aspect dispatcher.
That design explains Metalama’s runtime efficiency: the application executes the generated code rather than interpreting aspect templates on each invocation.
It does not make the added behavior free. Logging still performs I/O, contracts still execute checks, and large generated implementations can affect application size and JIT work.
Metalama’s documentation recommends keeping generated code concise and moving substantial shared logic into runtime helpers. Official explanation of aspect lifetime and generated code.
Why to use Metalama when we now generate boilerplate code with AI
Any source code, including AI-generated code, must be reviewed (it might have bugs), understood, maintained, while Metalama does not generate _source_ code, so it’s a good approach to reduce defect, reduce cognitive complexity of the source code, and even reduce tokens!
Write Your First Custom Aspect
For a console application, add the core package:
dotnet add package Metalama.Framework
Then Create a logging aspect:
using System;
using Metalama.Framework.Aspects;
public sealed class LogAttribute : OverrideMethodAspect
{
public override dynamic? OverrideMethod()
{
Console.WriteLine($"[log] Enter {meta.Target.Method.Name}");
try
{
return meta.Proceed();
}
catch (Exception exception)
{
Console.WriteLine($"[log] Failure: {exception.Message}");
throw;
}
finally
{
Console.WriteLine($"[log] Leave {meta.Target.Method.Name}");
}
}
}
Now apply it to a method:
public sealed class OrderService
{
[Log]
public decimal CalculateTotal(decimal price, int quantity)
=> price * quantity;
}
Two expressions explain the mechanism:
- meta.Target.Method.Name reads declaration metadata during compilation.
- meta.Proceed() represents execution of the original implementation.
The generated method retains the calculation, surrounds it with logging, and preserves exception propagation. Official introduction to method aspects.
Console logging keeps this example readable. In an application, adapt the pattern to your logging infrastructure and avoid exposing sensitive argument values.
Custom Contracts
A contract aspect can validate incoming values before the application uses them.
Here is a custom contract intended for string declarations:
using System;
using Metalama.Framework.Aspects;
public sealed class RequiredTextAttribute : ContractAspect
{
public override void Validate(dynamic? value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException(
"Text must not be empty or whitespace.",
nameof(value));
}
}
}
Use it on a parameter or property:
public sealed class Customer
{
[RequiredText]
public string Name { get; set; } = "Unknown";
public string Welcome([RequiredText] string name)
=> $"Welcome, {name}.";
}
A whitespace argument is rejected before Welcome executes. An invalid property assignment is rejected before it replaces the existing value.
Metalama substitutes nameof(value) with the target parameter’s name when generating parameter validation. Contracts can also normalize values and validate return values. Official documentation: custom contracts.
Introduce new members
Aspects can generate members as well as modify existing behavior:
using System;
using Metalama.Framework.Aspects;
public sealed class StampAttribute : TypeAspect
{
[Introduce]
public Guid TrackingId { get; } = Guid.NewGuid();
[Introduce]
public string DescribeStamp()
=> $"{meta.Target.Type.Name} / {TrackingId}";
}
Apply the aspect:
[Stamp]
public sealed partial class Shipment
{
public string Destination { get; set; } = "London";
}
The generated members are accessible through normal C#:
var shipment = new Shipment(); Console.WriteLine(shipment.TrackingId); Console.WriteLine(shipment.DescribeStamp());
The partial keyword matters. When source code references introduced members, Metalama requires the target type to be partial so those members are visible during design-time analysis. Without it, compilation can succeed while IntelliSense reports missing-member errors such as CS1061. Official documentation: introduced members.
Get the Most from Visual Studio for Metalama
The Visual Studio extension of Metalama makes aspects easier to inspect and understand.

To install it:
- Open Extensions → Manage Extensions.
- Search for Metalama + PostSharp.
- Select the extension and click Download.
- Follow the installation process and then Reopen Visual Studio and verify that the extension is installed.
See which aspects affect your code
See which aspects affect your code
CodeLens shows applied aspects and their origins. This is particularly helpful when a fabric applies an aspect without an attribute appearing on the method.

Metalama Diff compares original and transformed code. Open it through Preview Transformed Code in CodeLens or the document’s Metalama Diff context-menu command.

Aspect Explorer works in the other direction: choose an aspect and inspect the declarations it affects. Open it through Extensions → Metalama + PostSharp → Aspect Explorer. Understanding aspect-oriented code.


T# highlighting also helps distinguish compile-time expressions from generated runtime behavior. Together, these features make the transformation something you can inspect while developing. Getting started with Visual Studio tooling.
Debug the Transformed Code
Debugging generated behavior is a practical Visual Studio workflow. With Metalama it is a simple approach and requires minor configuration to setup your IDE for Metalama Debugging.
To see the actual transformed implementation:
- Open Build → Configuration Manager.
- Create a configuration named LamaDebug.
- Copy its settings from Debug.
- Select the new configuration and build.
- Start debugging and use F11 to step into execution.

Under this configuration, the debugger displays transformed code.
In Solution Explorer, enable Show All Files and locate the generated files beneath the intermediate output directory, typically:
obj/LamaDebug/net10.0/metalama
Set breakpoints in those transformed files to inspect generated logging or validation.

There is one important detail: in LamaDebug, breakpoints in the original source files are not hit because debugging maps to transformed files.
You can also use Debugger.Break() temporarily to enter the debugger. The formatting warning LAMA0066 is expected in this configuration. Official debugging walkthrough.
This lets you investigate the code your application actually executes, including the behavior added by aspects.
Explore Existing Aspects in the Marketplace
You do not have to implement every pattern yourself.
The documentation directs developers to the Metalama Marketplace for dozens of open-source aspects and extensions.
To find an existing implementation:
- Open the Metalama Marketplace.
- Look for the required pattern, such as contracts, observability, or caching. Browser Find can help locate a term.
- Review the category, author, quality, and license information.
- Open the entry’s documentation or repository.
- Follow its installation instructions and usage examples.

For a concrete example, install the contracts library:
dotnet add package Metalama.Patterns.Contracts
Then use its existing attributes:
using Metalama.Patterns.Contracts;
public sealed class DeliveryService
{
public string CreateLabel([NotNull] string recipient)
=> $"Deliver to: {recipient}";
}
The library supplies the null check. Other contract attributes cover strings, numeric ranges, collections, and more. Official contract API.
Contracts also support inherited enforcement and configurable code-generation conventions, making them useful beyond individual guard clauses. Contracts library documentation.
Here is a quick list of useful Aspects on Metalama’s marketplace:
#Metalama.Extensions.DependencyInjection
#Metalama.Patterns.Observability
#Metalama.Patterns.Memoization
#Metalama.Patterns.Caching
Find more Aspects on Metalama’s Marketplace.
Apply Aspects in Bulk with Fabrics
Attributes work well when each application should be explicit. For broad conventions, adding the same attribute repeatedly becomes another maintenance task.
Fabrics let you select declarations programmatically and apply aspects using LINQ-style queries.
Using the earlier LogAttribute, a project fabric can select public methods on public types:
using Metalama.Framework.Aspects;
using Metalama.Framework.Code;
using Metalama.Framework.Fabrics;
// Only public methods in this namespace receive logging automatically.
internal sealed class LoggingFabric : NamespaceFabric
{
public override void AmendNamespace(INamespaceAmender amender)
{
amender.SelectMany(ns => ns.Types)
.Where(type => type.Accessibility == Accessibility.Public)
.SelectMany(type => type.Methods)
.Where(method => method.Accessibility == Accessibility.Public)
.AddAspectIfEligible();
}
}
The selection follows the project’s code model, so newly added matching methods receive the aspect automatically.
This is dynamic selection during compilation, rather than runtime attachment of aspects. A ProjectFabric operates within its project; use a NamespaceFabric for a narrower namespace convention.
Fabrics suit broad policies such as tracing. More selective behaviors need care: caching every method whose name starts with Get can introduce incorrect behavior. Choose targets based on semantics and inspect the results with CodeLens and Metalama Diff. Official documentation: fabrics.
Metalama and AI Agents
An AI agent writing aspects needs accurate knowledge of templates, compile-time APIs, and the code model.
The Metalama plugin gives OpenAI Codex and Claude Code access to conceptual documentation, API references, examples, and pattern-library guidance.
For OpenAI Codex, add the marketplace:
codex plugin marketplace add metalama/Metalama.AI.Skills
codex plugin marketplace add metalama/Metalama.AI.Skills

Then open Plugins in the Codex app and install metalama.

After installation, try a focused request:

Similarly for Claude Code, you can add the Metalama AI Skills to the marketplace and start writing prompts which would efficiently generate Metalama code according to the best practices as provided through the Metalama skills.
For Claude Code, run:
/plugin marketplace add https://github.com/metalama/Metalama.AI.Skills
/plugin install metalama
Explicitly mention Metalama if the agent does not use its specialized guidance. Review the generated implementation and verify behavior as you would with any other code change. Official AI agent configuration.
Start Exploring Metalama Today
If recurring infrastructure code is making your services harder to read, Metalama is worth exploring. Start with one small aspect, inspect the transformation, and decide whether the abstraction makes your codebase easier to maintain.
Try Metalama: follow the official getting-started guide, build a logging aspect, and compare the original method with its generated implementation.
Thanks to PostSharp Technologies for the substantial effort invested in bringing these tools to the .NET community, and for the collaboration on this post.
Final Thoughts
Aspect-Oriented Programming gives recurring behavior a reusable implementation. Metalama makes that practical through compile-time metaprogramming, custom aspects, existing libraries, and fabrics.
My recommendation is to introduce it when a pattern already repeats and its rules are clear. Logging conventions, contracts, and property-notification patterns are useful starting points.
Thinking back to the repeated Stopwatch and logging code in that project, reusable method aspects are what I particularly like about Metalama.
Realizing that reducing boilerplate also means reducing the number of places I need to maintain. That is where Metalama becomes especially useful: keeping recurring technical concerns consistent while making business logic easier to read. This is the approach I always wished I had.
It may add unnecessary complexity to a small application with little repetition. Business decisions that deserve explicit code, runtime plugin discovery, and behavior already handled cleanly by middleware or decorators may fit other approaches better.
The best adoption is gradual: choose a pattern, inspect the generated code, test its behavior, and expand only when the result is easier for the team to understand.
References
- Metalama documentation
- Getting started
- Differences from PostSharp
- Writing T# templates
- Custom contracts
- Introducing members
- Installing Visual Studio tooling
- Understanding transformed code
- Debugging transformed code
- Getting existing aspects
- Contracts library
- Applying aspects with fabrics
- Configuring AI agents
- Registering a license
- PostSharp Technologies on LinkedIn
Bonus
Sharing with you this brilliant compilation for Chopin’s best Noctures through an AI Story Art by Classical Oasis.
Enjoy it while having fun reading, learning, and coding with Metalama.
