Note
If you don't plan to create your own aspects but just use existing ones, start with Using Metalama.
1. Add Metalama to your project
Add the Metalama.Framework package to your project.
Note
If your project targets the .NET Framework or .NET Standard, you may also need to add PolySharp, which updates the language version even if it's officially unsupported.
2. Configure your IDE (optional)
For the best design-time experience, configure your IDE. See Configuring your IDE for details. If you're using Visual Studio, install Visual Studio Tools for Metalama. The extension provides:
- AspectDiff: Displays a side-by-side comparison of source code with the generated code.
- CodeLens: Displays which aspects are applied to your code.
- Aspect Explorer: Navigates from aspects to their target declarations.
- Syntax highlighting: Highlights compile-time code in templates, which is particularly useful when you're getting started.
3. Create an aspect class
Let's start with logging, the traditional Hello, world example of aspect-oriented programming.
Type the following code:
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.GettingStarted;
5
6public class LogAttribute : OverrideMethodAspect
7{
8 public override dynamic? OverrideMethod()
9 {
10 Console.WriteLine( $"Entering {meta.Target.Method}" );
11
12 try
13 {
14 return meta.Proceed();
15 }
16 finally
17 {
18 Console.WriteLine( $"Leaving {meta.Target.Method}" );
19 }
20 }
21}
As you can infer from its name, the LogAttribute class is a custom attribute. You can think of an aspect as a template. When you apply it to some code (in this case, to a method), it transforms it. Indeed, the code of the target method will be replaced by the implementation of OverrideMethod. This method is very special. Some parts execute at run time, while others, which typically start with the meta keyword, execute at compile time. If you installed Visual Studio Tools for Metalama, you'll notice that compile-time segments are displayed with a different background color.
Let's examine two meta expressions:
meta.Proceed()is replaced by the code of the target method.meta.Target.Methodgives you access to the IMethod code model. In this case, we're implicitly callingToString().
4. Apply the custom attribute to a method
Remember that an aspect is a template and that it doesn't do anything until it's applied to some target code.
So, let's add the [Log] attribute to some method:
1using System;
2
3namespace Doc.GettingStarted;
4
5internal class Foo
6{
7 [Log]
8 public void Method1()
9 {
10 Console.WriteLine( "Hello, world." );
11 }
12}
Now, if you execute the method, the following output is printed:
Entering Foo.Method1()
Hello, world.
Leaving Foo.Method1()
5. See what happened to your code
You can see that Metalama didn't modify anything in your source code. It's still yours. Instead, Metalama applied the logging aspect during compilation. So, it's no longer your source code that's being executed, but your source code enhanced by the logging aspect.
If you installed Visual Studio Tools for Metalama, you can compare your source code with the transformed (executed) code using the "Diff preview" feature accessible from the source file context menu in Visual Studio.
It will show you something like this:
1using System;
2
3namespace Doc.GettingStarted;
4
5internal class Foo
6{
7 [Log]
8 public void Method1()
9 {
10 Console.WriteLine( "Hello, world." );
11 }
12}
1using System;
2
3namespace Doc.GettingStarted;
4
5internal class Foo
6{
7 [Log]
8 public void Method1()
9 {
10 Console.WriteLine("Entering Foo.Method1()");
11 try
12 {
13 Console.WriteLine("Hello, world.");
14 return;
15 }
16 finally
17 {
18 Console.WriteLine("Leaving Foo.Method1()");
19 }
20 }
21}
6. Add aspects in bulk using fabrics
With aspects like logging, it's frequently applied to a large number of methods. It would be cumbersome to add a custom attribute to each of them. Instead, let's see how we can add the aspect programmatically using fabrics.
Use the following code:
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using Metalama.Framework.Fabrics;
4
5namespace Doc.GettingStarted_Fabric;
6
7public class Fabric : ProjectFabric
8{
9 public override void AmendProject( IProjectAmender amender )
10 {
11 amender
12 .SelectMany( compilation => compilation.AllTypes )
13 .Where( type => type.Accessibility is Accessibility.Public )
14 .SelectMany( type => type.Methods )
15 .Where( method => method.Accessibility is Accessibility.Public )
16 .AddAspectIfEligible<LogAttribute>();
17 }
18}
This class derives from ProjectFabric and acts as a compile-time entry point for the project. As you can see, it adds the logging aspect to all public methods of all public types.
7. Add architecture validation
Note
This feature requires a Metalama Professional license.
Now that you know about aspects and fabrics, it's easy to understand how to validate your codebase against some architectural rules. In this example, we'll show how to report a warning when internals of a namespace are used outside of this namespace.
First, reference the Metalama.Extensions.Architecture package from your project.
Then, add a fabric with the validation logic. We can use a ProjectFabric as above:
1using Metalama.Extensions.Architecture;
2using Metalama.Extensions.Architecture.Predicates;
3using Metalama.Framework.Fabrics;
4
5namespace Doc.GettingStarted_Architecture;
6
7internal class Fabric : ProjectFabric
8{
9 public override void AmendProject( IProjectAmender amender )
10 {
11 const string ns = "Doc.GettingStarted_Architecture.VerifiedNamespace";
12
13 amender
14 .Select( compilation => compilation.GlobalNamespace.GetDescendant( ns )! )
15 .CanOnlyBeUsedFrom( r => r.Namespace( ns ) );
16 }
17}
Alternatively, we can achieve the same with a NamespaceFabric, which acts within the scope of their namespace instead of their project:
1using Metalama.Extensions.Architecture;
2using Metalama.Extensions.Architecture.Predicates;
3using Metalama.Framework.Fabrics;
4
5namespace Doc.GettingStarted_Architecture_Ns
6{
7 namespace VerifiedNamespace
8 {
9 internal class Fabric : NamespaceFabric
10 {
11 public override void AmendNamespace( INamespaceAmender amender )
12 {
13 amender
14 .CanOnlyBeUsedFrom( r => r.CurrentNamespace() );
15 }
16 }
17 }
18}
Fabrics not only run at compile time but also at design time within the IDE. After the first build (or after you click the I am done with compile-time changes link if you've installed Metalama Tools for Visual Studio), you'll see warnings in the IDE if your code violates the rule.
In this case, when we try to access any class of VerifiedNamespace from a different namespace, we get a warning:
1using Doc.GettingStarted_Architecture.VerifiedNamespace;
2
3namespace Doc.GettingStarted_Architecture
4{
5 namespace VerifiedNamespace
6 {
7 internal class Foo { }
8
9 internal class AllowedInheritor : Foo { }
10 }
11
12 namespace OtherNamespace
13 {
Warning LAMA0905: The 'Doc.GettingStarted_Architecture.VerifiedNamespace' namespace cannot be referenced by the 'Doc.GettingStarted_Architecture.OtherNamespace' namespace.
14 internal class ForbiddenInheritor : Foo { }
15 }
16}
Conclusion
Congratulations! In this short tutorial, you've discovered the key concepts of Metalama: aspects and fabrics. You've learned how to transparently add behaviors to your code during compilation and add validation rules that get enforced in real time in the editor.
From here, you can explore further based on your learning style: