Thursday 2 May 2019

Generic Builder

At the beginning of this previous post I showed one common pattern for Immutable classes in C#, having a setPropertyX method in the immutable class that returns a copy of the object with the new property value (I think this is called chained setters). Another more basic approach is using the Builder pattern. I think this is more basic because you are not changing certain properties, you are passing the values for all the properties. This means we'll have constructors that can have a large number of parameters, and this is one of the use cases of the Builder pattern. You can see a Java example here, and I put my own C# sample below:

using System;

namespace Builder
{
    
public class Person
    {
        public string Name {get; private set;}
        public int Age {get; private set;}

        public Person(string name, int age)
        {
            this.Name = name;
            this.Age = age;
        }
    }

public class PersonBuilder
    {
        private string Name;
        private int Age;

        public PersonBuilder SetName(string name)
        {
            this.Name = name;
            return this;
        }

        public PersonBuilder SetAge(int age)
        {
            this.Age = age;
            return this;
        }

        public Person Build()
        {
            return new Person(this.Name, this.Age);
        }
    }

var person = new PersonBuilder()
                .SetAge(20)
                .SetName("Xuan")
                .Build();


Manually writing a Builder class for each immutable class that you want in your application can be a bit of a repetitive pain so, could we have something more generic? Well, here's my try at building a GenericBuilder in C#.

   public class GenericBuilder<T>
    {
        private Dictionary parametersDic = new Dictionary<string, object>();

        public GenericBuilder<T> SetValue(string key, object param)
        {
            //the values are given in PascalCase but the constructor params are obviously in CamelCase
            this.parametersDic[this.toCamelCase(key)] = param;
            return this;
        }

        public T Build()
        {
            var ctors = typeof(T).GetConstructors();
            var ctor = ctors.FirstOrDefault(it => it.GetParameters().Length == this.parametersDic.Count);
            if (ctor != null)
            {
                var orderedParams = new List<object>();
                foreach (var param in ctor.GetParameters())
                {
                    orderedParams.Add(this.parametersDic[param.Name]);
                }
                return (T) ctor.Invoke(orderedParams.ToArray());
            }
            else
            {
                throw new Exception("Not Suitable Constructor Found");
            }
        } 

        private string toCamelCase(string st) => char.ToLower(st[0])+ st.Substring(1);
	}
	

That we can use like this to build an instance of the aforementioned Person class:

		
	var person =  new GenericBuilder<Person>()
	.SetValue("Age", 1)
	.SetValue("Name", "Francois")
	.Build();

We set values passing the property/field name and the value and store it in a Dictionary. When we finally want to create the object we invoke the constructor via reflection passing the values in the correct order expected by the constructor.

This is the first time that I make use of ConstructorInfo to create an object, so far I'd always used Activator.CreateInstance. For this case, we need to use GetConstructors to obtain the constructor, cause we don't know the parameters order and hence we can not use Activator.CreateInstace. In other cases both options would be equally valid, for example:

//Factory method creating an object that expects a string as parameter to its constructor
        public static T ItemFactory1<T>(string id)
        {
            return (T) Activator.CreateInstance(typeof(T), new [] {id});
        }

        public static T ItemFactory2<T>(string id)
        {	
            return (T) typeof(T).GetConstructor(new [] {typeof(string)})
                .Invoke(new [] {id});
        }

I guess Activator.CreateInstance has to locate the constructor in the same (costly) way that GetConstructor does, and it has to do it each time we want to create an object. If we use GetConstructor, we can call it once and store it for ensuing calls, so if we are going to create multiple objects it's a better option. It is is mentioned in one of the comments here.

No comments:

Post a Comment