Skip to content

macsux/builder-pattern-generator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This project demonstrates the use of .NET Source generators in order to automatically generate builder pattern for classes. Full details on this project are available in this blog post.

The code has been updated in 2024 since that blog was written to support new syntax constructs in C#. This builder implementation creates an immutable pattern that minimizes memory heap allocations and minimizes excessive cloning.

What does it do

For code like this:

[GenerateBuilder]
public partial class Dog
{
    public required string Name {get; init;}
    public string Breed {get; init;}
}

It allows you to do this:

// start from initializer syntax
var dog = new Dog
{
    Name = "Drake",
    Breed = "Husky"
};

// start from builder
dog = new Dog.DogBuilder()
    .WithName("Drake")
    .WithBreed("Husky")
    .Build();
    
    
var anotherDog = dog.Builder
    .WithName("WallE")
    .Build(); // clone dog with new name

var builder = new Dog.DogBuilder();
builder.WithBreed("Husky").Build(); // throws because required property Name is not set

By generating this:

partial class Dog
{
    public DogBuilder Builder => new DogBuilder(this);
    public struct DogBuilder
    {
        private byte _set;
        Dog _original;

        public DogBuilder(Dog original)
        {
            _original = original;
        }
        
        private string _name;
        public DogBuilder WithName(string name)
        {
            _name = name;
            _set |= 1;
            return this;
        }
        private bool IsNameSet => (_set & 1) == 1;
        
        private string _breed;
        public DogBuilder WithBreed(string breed)
        {
            _breed = breed;
            _set |= 2;
            return this;
        }
        private bool IsBreedSet => (_set & 2) == 2;
        
        

        public Dog Build()
        {
            if(_original == null)
            {
                 if(!IsNameSet)
                 {
                     var message = $"The following required properties have not been set: {(!IsNameSet ? "Name" : "")}, ";
                     throw new InvalidOperationException(message.TrimEnd(',',' '));
                 }

                return new Dog
                {
                    Name = _name,Breed = _breed
                };
            }

            if(IsNameSet && !object.Equals(_name, _original.Name))
            {
                goto clone;
            }
            if(IsBreedSet && !object.Equals(_breed, _original.Breed))
            {
                goto clone;
            }
            
           return _original;
           clone:
           return new Dog
           {
            Name = IsNameSet ? _name : _original.Name,
            Breed = IsBreedSet ? _breed : _original.Breed
           };
       }
   }
}

About

.NET source generator for builder pattern

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages