12

In Java and C#, you can create an object with properties that can be set at initialisation by either defining a constructor with parameters, defining each property after constructing the object, or using the builder/fluid interface pattern. However, C# 3 introduced object and collection initialisers, which meant the builder pattern was largely useless. In a language without initialisers, one could implement a builder then use it like so:

Vehicle v = new Vehicle.Builder()
                    .manufacturer("Toyota")
                    .model("Camry")
                    .year(1997)
                    .colour(CarColours.Red)
                    .addSpecialFeature(new Feature.CDPlayer())
                    .addSpecialFeature(new Feature.SeatWarmer(4))
                    .build();

Conversely, in C# one could write:

var vehicle = new Vehicle {
                Manufacturer = "Toyota",
                Model = "Camry",
                Year = 1997,
                Colour = CarColours.Red,
                SpecialFeatures = new List<SpecialFeature> {
                    new Feature.CDPlayer(),
                    new Feature.SeatWarmer { Seats = 4 }
                }
              }

...eliminating the need for a builder as seen in the previous example.

Based on these examples, are builders still useful in C#, or have they been superseded entirely by initialisers?

svbnet
  • 231

3 Answers3

12

Object initializers require that the properties must be accessible by the calling code. Nested builders may access private members of the class.

If you wanted to make Vehicle immutable (via making all setters private), then the nested builder could be used to set the private variables.

12

As touched on by @user248215 the real issue is immutability. The reason you would use a builder in C# would be to maintain the explicitness of an initializer without having to expose settable properties. It is not a question of encapsulation which is why I have written my own answer. Encapsulation is fairly orthogonal as invoking a setter does not imply what the setter actually does or tie you to its implementation.

The next version of C#, 9.0, is likely to introduce a with keyword which will allow for immutable objects to be initialized clearly and concisely without needing to write builders.

Another interesting thing you can do with builders, as opposed to initializers, is that they can result in different types of objects depending on the sequence of methods called.

For example

value.Match()
    .Case((DateTime d) => Console.WriteLine($"{d: yyyy-mm-dd}"))
    .Case((double d) => Console.WriteLine(Math.Round(d, 4));
    // void

var str = value.Match() .Case((DateTime d) => $"{d: yyyy-mm-dd}") .Case((double d) => Math.Round(d, 4).ToString()) .ResultOrDefault(string.Empty); // string

*Just to clarify the example above, it is a pattern matching library that uses the builder pattern to build up a "match" by specifying cases. Cases are appended by calling the Case method passing it a function. If value is assignable to the function's parameter type it is invoked. You can find the full source code on GitHub and, since XML comments are hard to read in plain text, here are some unit tests that show it in action.

Aluan Haddad
  • 698
  • 4
  • 9
1

They all serve different purposes!!!

Constructors can initialize fields that are marked readonly as well as private and protected members. However, you are somewhat limited in what you can do inside a constructor; you should avoid passing this to any external methods, for example, and you should avoid calling virtual members, since they may execute in the context of a derived class that hasn't constructed itself yet. Also, constructors are guaranteed to run (unless the caller is doing something very unusual), so if you set fields in the constructor, code in the rest of the class can assume that those fields are not going to be null.

Initializers run after construction. They only let you call public properties; private and/or readonly fields cannot be set. By convention, the act of setting a property should be pretty limited, e.g. it should be idempotent, and have limited side effects.

Builder methods are actual methods and therefore allow more than one argument, and by convention can have side effects including object creation. While they cannot set readonly fields, they can pretty much do anything else. Also, methods can be implemented as extension methods (like pretty much all of the LINQ functionality).

John Wu
  • 26,955