Joshua Bloch's Builder Pattern in C#
Having spent a lot of time programming in Java over the last two years, I've made heavy use of Joshua Bloch's Java Builder pattern (also Effective Java Item 2).
Recently, I've started a fairly large project in C# 3.0. As it happens, there came a point where I wanted to use a pattern similar to Bloch's Builder. However, as anyone who tries to use this pattern in C# will quickly find out, Bloch's Builder doesn't translate perfectly to C#.
Here is how Bloch's Builder looks in C# (with changes made only for syntax):
public class NutritionFacts
{
public int ServingSize { get; private set; }
public int Servings { get; private set; }
public int Calories { get; private set; }
...
public class Builder
{
private int ServingSize { get; set; }
private int Servings { get; set; }
private int Calories { get; set; }
public Builder(int servingSize, int servings)
{
ServingSize = servingSize;
Servings = servings;
}
public Builder Calories(int calories)
{ Calories = calories; return this; }
...
public NutritionFacts Build()
{
return new NutritionFacts(this);
}
}
private NuitritionFacts(Builder builder)
{
ServingSize = builder.ServingSize;
Servings = builder.Servings;
Calories = builder.Calories;
...
}
}
If you try to compile that code, the C# compiler will complain, saying that it does not have permission to access the private members of the Builder class.
Then how come it worked in Java? The answer is that Java has a special rule that allows Java outer classes to access the private members of their inner classes. In C#, this rule does not exist, and thus C# outer classes cannot access the private members of C# inner classes.
Since Bloch's Builder pattern doesn't work in C#, the natural next question is: how do we make it work?
Approach 1: Make Builder Properties Public
The C# compiler tells us that it can't compile because it doesn't have permission to access private members of the nested Builder class. Thus, the most obvious solution to this is to make them public:
public class NutritionFacts
{
...
public class Builder
{
public int ServingSize { get; set; }
public int Servings { get; set; }
public int Calories { get; set; }
...
}
...
}
This allows the code to compile, and we can use the Builder just as we used it in Java.
However there is one niggling concern: the C# Builder exposes getters in the Builder object. In the Java Builder, when we were constructing an object, we were only able to use methods that contributed to building the object. That is, the Java Builder doesn't expose any getters. However, in the C# version, our API is polluted by unneeded getters. Let's see if we can do better.
Approach 2: Build the object in the Builder
As we've already seen, C# does not permit outer classes to access the private members of inner classes. However, C# does let us access outer classes from inner classes. Since that's the case, why don't we invert the building procedure. That is, let's build the object in the Builder, instead of in the class being built:
public class NutritionFacts
{
public int ServingSize { get; private set; }
public int Servings { get; private set; }
public int Calories { get; private set; }
...
public class Builder
{
private int ServingSize { get; set; }
private int Servings { get; set; }
private int Calories { get; set; }
public Builder(int servingSize, int servings)
{
ServingSize = servingSize;
Servings = servings;
}
public Builder Calories(int calories)
{ Calories = calories; return this; }
...
public NutritionFacts Build()
{
return new NutritionFacts
{
ServingSize = ServingSize,
Servings = Servings,
Calories = Calories,
...
};
}
}
private NuitritionFacts()
{
// Intentionally empty.
}
}
That, to me, is the closest you can get to the Java pattern. From an API consumer's point of view, it'll look identical to a Java Builder.
Alternative Form
There is another alternative to the two C# Builders mentioned earlier in this article. As it stands, Bloch's Builder can be used as a sort of prototype factory. You can configure the builder and then pump out instances of the class that have that configuration. However, if you're using the Builder to make a single, immutable instance, a more efficient way would be to avoid copying the data first to the Builder and then to the new instance.
Here's a version of the Builder (by Jon Skeet) that can be used only once, but avoids unnecessary data copying.
public class NutritionFacts
{
public int ServingSize { get; private set; }
public int Servings { get; private set; }
public int Calories { get; private set; }
...
public class Builder
{
private NutritionFacts _facts = new NutritionFacts();
public Builder(int servingSize, int servings)
{
_facts.ServingSize = servingSize;
_facts.Servings = servings;
}
public Builder Calories(int calories)
{ _facts.Calories = calories; return this; }
...
public NutritionFacts Build()
{
var ret = facts;
// Invalidates builder to maintain mutability.
facts = null;
return ret;
}
}
private NuitritionFacts()
{
// Intentionally empty.
}
}
Some of the advantages of this pattern are:
- No data copying is required at build time. In other words, while you're setting the properties, you're doing so on the real object - you just can't see it yet. This is similar to what StringBuilder does.
- The builder becomes invalid after calling Build() to guarantee immutability. This unfortunately means it can't be used as a sort of "prototype" in the way that Bloch's version can.
Conclusion
In this article we've looked at 3 different working C# implementations of Bloch's Builder pattern. The first approach was a quick and dirty fix that got the job done. The second approach applied a bit more finesse and managed to keep the same API as the Java Builder. The final approach was more of a variation on the second approach, offering a more efficient implementation of the pattern if you're not using the Builder as an object prototype factory.
5 comments