9

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?

  • are builders useful why do I keep seeing these sorts of questions? A Builder is a design pattern - sure, it may be exposed as a language feature but at the end of the day it's just a design pattern. A design pattern cannot be "wrong". It may or may not fulfil your use case but that doesn't make the entire pattern wrong, just the application of it. Remember that at the end of the day design patterns solve particular problems - if you don't face the problem, then why would you try solving it? – VLAZ Oct 1 '16 at 10:27
  • @vlaz I'm not saying builders are wrong - in fact my question was asking if there were any use cases for builders given initialisers are an implementation of the most common case where you would use builders. Obviously, people have answered that a builder is still useful for setting private fields, which in turn has answered my question. – svbnet Oct 1 '16 at 10:45
  • 3
    @vlaz To clarify: I am saying that initialisers have largely replaced the "traditional" builder/fluid interface pattern of writing an inner builder class, then writing chaining setter methods inside that class which create a new instance of that parent class. I am not saying that initialisers are a replacement for that specific builder pattern; I am saying that initialisers save developers having to implement that specific builder pattern, save for the use cases mentioned in the answers, which does not make the builder pattern useless. – svbnet Oct 1 '16 at 11:10
  • 5
    @vlaz I don't understand your concern. "You were saying that a design pattern is not useful" - no, Joe was asking if a design pattern is useful. How is that a bad, wrong, or mistaken question? Do you think the answer is obvious? I don't think the answer is obvious; this seems like a good question to me. – Tanner Swett Oct 1 '16 at 14:30
  • 2
    @vlaz, the singleton and service locator patterns are wrong. Ergo design patterns can be wrong. – David Arno Oct 3 '16 at 15:13
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#, 8.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 is a link to the SandCastle built documentation (see the Remarks section)

  • I don't see how this couldn't be done an object initialiser, using a IEnumerable<Func<T, TResult>> member. – Caleth Sep 11 '17 at 8:48
  • @Caleth That was actually something I experimented with but there are some issues with that approach. It doesn't allow for conditional clauses (not shown but used and demonstrated in the linked documentation) and it does not allow for type inference of TResult. Ultimately type-inference had a lot to do with it. I also wanted it to "look" like a control structure. Also I didn't want to use an initializer since that would allow mutation. – Aluan Haddad Sep 11 '17 at 15:02
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.

0

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).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.