[Home](../) › [Blog](../blog) › Implementing the Builder pattern with Metalama

# Implementing the Builder pattern with Metalama

> This article shows how to implement the Builder pattern automatically with Metalama. It starts discussing the implementation strategy, then comments the source code of the aspect.

Published: 2024-10-07 · Author: Metalama Team · Canonical: https://postsharp.net/blog/builder-pattern-with-metalama

The popularity of _immutable_ objects has made the Builder pattern one of the most important in C#. However, implementing the Builder pattern by hand is a tedious and repetitive task. Because it is repetitive, it can be automated using a Metalama aspect. We will start with the implementation strategy, then walk through the source code of the aspect.

## Components of the Builder pattern

As with any pattern automation, the very first step is to describe how we would implement the process _by hand_. It's a good practice to start with a few code snippets _before_ transformation and to handwrite the code we want to generate. Once we think we have covered all the cases we can identify, we can reason about how to turn this into an algorithm.

### Features

A proper implementation of the Builder pattern should include the following features:

* A `Builder` constructor accepting all required properties.
* A writable property in the `Builder` type corresponding to each property of the build type. For properties returning an immutable collection, the property of the `Builder` type should be read-only but return the corresponding _mutable_ collection type.
* A `Builder.Build` method returning the built immutable object.
* The ability to call an optional `Validate` method when an object is built.
* In the source type, a `ToBuilder` method returning a `Builder` initialized with the current values.

### Examples

Let's start with a simple example:

```c#
[GenerateBuilder]
public partial class Song
{
    [Required] public string Artist { get; }

    [Required] public string Title { get; }

    public TimeSpan? Duration { get; }

    public string Genre { get; } = "General";
}
```

We want the `[GenerateBuilder]` aspect to generate the following code:

```c#
public partial class Song
{
    private Song(string artist, string title, TimeSpan? duration, string genre)
    {
        Artist = artist;
        Title = title;
        Duration = duration;
        Genre = genre;
    }

    public Builder ToBuilder() => new Builder(this);

    public class Builder
    {
        // Public constructor.
        public Builder(string artist, string title)
        {
            Artist = artist;
            Title = title;
        }

        // Copy constructor.
        internal Builder(Song source)
        {
            Artist = source.Artist;
            Title = source.Title;
            Duration = source.Duration;
            Genre = source.Genre;
        }

        public string Artist { get; set; }
        public TimeSpan? Duration { get; set; }
        public string Genre { get; set; } = "General";
        public string Title { get; set; }

        public Song Build()
        {
            var instance = new Song(Artist, Title, Duration, Genre)!;
            return instance;
        }
    }
}
```

By the end of this article, we will be able to generate this code automatically on-the-fly during the build.

Here are some use cases for this code:

```csharp
// Use case 1. Create from scratch.
var songBuilder = new Song.Builder( "Joseph Kabasele", "Indépendance Cha Cha" );
songBuilder.Genre = "Congolese rumba";
var song = songBuilder.Build();

// Use case 2. Create builder from existing object.
var songBuilder2 = song.ToBuilder();
songBuilder2.Duration = new TimeSpan(0, 3, 5);
var song2 = songBuilder.Build();
```

### Implementation steps

Our `[GenerateBuilder]` aspect will need to perform the following steps:

- Introduce a nested class named `Builder` with the following members:
    - A _copy constructor_ initializing the `Builder` class from an instance of the source class.
    - A _public constructor_ for users of our class, accepting values for all required properties.
    - A writable property for each property of the source type.
    - A `Build` method that instantiates the source type with the values set in the `Builder`, calling the `Validate` method if present.
- Add the following members to the source type:
    - A private constructor called by the `Builder.Build` method.
    - A `ToBuilder` method returning a new `Builder` initialized with the current instance.

## Implementing the Builder pattern

In this article, I will only outline the major steps of the implementation. For a detailed implementation, see the [Builder pattern example](https://doc.metalama.net/examples/builder) in the reference documentation.

### Step 1. Create a Metalama aspect

The first step is to add the `Metalama.Framework` package to your project:

```xml
<ItemGroup>
    <PackageReference Include="Metalama.Framework"/>
</ItemGroup>
```

Then, create an aspect class:

```csharp
public partial class GenerateBuilderAttribute : TypeAspect
```

The `TypeAspect` class is an abstract base class for aspects that can be applied to types.

### Step 2. Define some infrastructure

Before adding anything to the aspect, we need a data structure to store references to the declarations we generate. The `PropertyMapping` type maps a property of the source code to its corresponding property in the `Builder` type and in constructor parameters.

```csharp
[CompileTime]
private record Tags(
    INamedType SourceType,
    IReadOnlyList<PropertyMapping> Properties,
    IConstructor SourceConstructor,
    IConstructor BuilderCopyConstructor );

[CompileTime]
private class PropertyMapping
{
    public PropertyMapping( IProperty sourceProperty, bool isRequired )
    {
        this.SourceProperty = sourceProperty;
        this.IsRequired = isRequired;
    }

    public IProperty SourceProperty { get; }

    public bool IsRequired { get; }

    public IProperty? BuilderProperty { get; set; }

    public int? SourceConstructorParameterIndex { get; set; }

    public int? BuilderConstructorParameterIndex { get; set; }
}
```

Note that we added the `[CompileTime]` attribute to these classes because they need to be accessible at _compile time_ by the aspect.

The full source code of examples in this article is available on [GitHub](https://github.com/metalama/Metalama.Samples/tree/HEAD/src/builder/builder-1).

### Step 3. Identify the properties to be mapped

We can now start implementing the aspect. Its entry point is the `BuildAspect` method. The first thing we do is create a list of properties.

```csharp
public override void BuildAspect( IAspectBuilder<INamedType> builder )
{
    base.BuildAspect( builder );

    var sourceType = builder.Target;

    // Create a list of PropertyMapping items for all properties that we want to build using the Builder.
    var properties = sourceType.Properties.Where( p => p.Writeability != Writeability.None &&
                                                       !p.IsStatic )
        .Select( p => new PropertyMapping(
                     p,
                     p.Attributes.OfAttributeType( typeof(RequiredAttribute) ).Any() ) )
        .ToList();
```

### Step 4. Introduce the Builder type and its properties

Let's create a nested type using the `IntroduceClass` method:

```csharp
// Introduce the Builder nested type.
var builderType = builder.IntroduceClass(
    "Builder",
    buildType: t => t.Accessibility = Accessibility.Public );
```

Now we can add properties to our new `Builder` type:

```csharp
// Add builder properties and update the mapping.
foreach ( var property in properties )
{
    property.BuilderProperty =
        builderType.IntroduceAutomaticProperty(
                property.SourceProperty.Name,
                property.SourceProperty.Type,
                buildProperty: p =>
                {
                    p.Accessibility = Accessibility.Public;
                    p.InitializerExpression = property.SourceProperty.InitializerExpression;
                } )
            .Declaration;
}
```

### Step 5. Creating the Builder public constructor

Our next task is to create the public constructor of the `Builder` nested type, which should have parameters for all required properties. Let's add this code to the `BuildAspect` method:

```csharp
// Add a builder constructor accepting the required properties and update the mapping.
builderType.IntroduceConstructor(
    nameof(this.BuilderConstructorTemplate),
    buildConstructor: c =>
    {
        c.Accessibility = Accessibility.Public;

        foreach ( var property in properties.Where( m => m.IsRequired ) )
        {
            var parameter = c.AddParameter(
                NameHelper.ToParameterName( property.SourceProperty.Name ),
                property.SourceProperty.Type );

            property.BuilderConstructorParameterIndex = parameter.Index;
        }
    } );
```

Here is `BuilderConstructorTemplate`, the template for this constructor. You can see how we use the `Tags` and `PropertyMapping` objects. This code iterates through required properties and assigns a property of the `Builder` type to the value of the corresponding constructor parameter.

```csharp
[Template]
private void BuilderConstructorTemplate()
{
    var tags = (Tags) meta.Tags.Source!;

    foreach ( var property in tags.Properties.Where( p => p.IsRequired ) )
    {
        property.BuilderProperty!.Value =
            meta.Target.Parameters[property.BuilderConstructorParameterIndex!.Value].Value;
    }
}
```

### Step 6. Adding a constructor to the source type

Before we implement the `Build` method, we must implement the constructor in the source type. This code snippet from the `BuildAspect` method creates the constructor and its parameters:

```csharp
// Add a constructor to the source type with all properties.
var sourceConstructor = builder.IntroduceConstructor(
        nameof(this.SourceConstructorTemplate),
        buildConstructor: c =>
        {
            c.Accessibility = Accessibility.Private;

            foreach ( var property in properties )
            {
                var parameter = c.AddParameter(
                    NameHelper.ToParameterName( property.SourceProperty.Name ),
                    property.SourceProperty.Type );

                property.SourceConstructorParameterIndex = parameter.Index;
            }
        } )
    .Declaration;
```

The template for this constructor is `SourceConstructorTemplate`. It simply assigns properties based on constructor parameters.

```csharp
[Template]
private void SourceConstructorTemplate()
{
    var tags = (Tags) meta.Tags.Source!;

    foreach ( var property in tags.Properties )
    {
        property.SourceProperty.Value =
            meta.Target.Parameters[property.SourceConstructorParameterIndex!.Value].Value;
    }
}
```

### Step 7. Implementing the Build method

The `Build` method of the `Builder` type is responsible for creating an instance of the source (immutable) type from the values of the `Builder`.

```csharp
// Add a Build method to the builder.
builderType.IntroduceMethod(
    nameof(this.BuildMethodTemplate),
    IntroductionScope.Instance,
    buildMethod: m =>
    {
        m.Name = "Build";
        m.Accessibility = Accessibility.Public;
        m.ReturnType = sourceType;
    } );
```

The T# template for the `Build` method first invokes the newly introduced constructor, then tries to find and call the optional `Validate` method before returning the new instance of the source type.

```csharp
[Template]
private dynamic BuildMethodTemplate()
{
    var tags = (Tags) meta.Tags.Source!;

    // Build the object.
    var instance = tags.SourceConstructor.Invoke( tags.Properties.Select( x => x.BuilderProperty! ) )!;

    // Find and invoke the Validate method, if any.
    var validateMethod = tags.SourceType.AllMethods.OfName( "Validate" )
        .SingleOrDefault( m => m.Parameters.Count == 0 );

    if ( validateMethod != null )
    {
        validateMethod.WithObject( (IExpression) instance ).Invoke();
    }

    // Return the object.
    return instance;
}
```

### Next implementation steps

I hope the previous steps gave you an idea of how Metalama works. Automating the implementation of the Builder pattern requires a few more steps, all covered in the [Builder pattern example](https://doc.metalama.net/examples/builder):

* Generating the `ToBuilder` method
* Coping with base and derived types
* Handling collection types

## Is it worth it?

As you can see, even with Metalama, automating the Builder pattern is not completely trivial. So, is it worth it?

It depends on how often the aspect will be used in your application. Typically, if an aspect is used fewer than a dozen times, automation may not be worthwhile. However, if you're planning a large project with dozens or even hundreds of classes that would benefit from the builder pattern, then automating it is definitely worth the effort.

Remember, every project will have slightly different requirements for implementing the Builder pattern. To save time, start with the [Builder pattern example](https://doc.metalama.net/examples/builder), understand its principles, and customize it to your needs.

The benefit of automating the pattern implementation as an aspect is that when you want to change the pattern, you only have to edit a _single_ class: the aspect.

## Wrapping up

The Builder pattern has become one of the most important patterns in modern .NET, thanks to its focus on immutability. With the Builder pattern, you get the convenience of mutability during the configuration stage of a component, coupled with the safety and simplicity of immutability after the component has been built.

Implementing the Builder pattern traditionally involves a lot of boilerplate code. Fortunately, tools like Metalama make it easier to automate its implementation.

---

## Site navigation

- Index of the whole site: [llms.txt](../llms.txt)

