So it’s been a little while since my last blog post due to the fact that I have recently moved to London to start a new job. If you’re involved in the .NET community in London then look out for me at various conferences and user groups in the new year!
Following on from my previous post which explained the concept of an object who’s publicly exposed interface adapted based on its state, in this post I’ll explain how the technique can be used to create an elegant implementation of the Builder Pattern with a fluent interface.
For the example we’ll take a simple immutable object which represents a Car:
public class Car
{
public EngineBase Engine { get; private set; }
public SeatBase DriversSeat { get; private set; }
public SeatBase PassengerSeat { get; private set; }
public WheelType Wheels { get; private set; }
public BreakeBase Brakes { get; private set; }
}
In order to construct a new Car object, we would have to introduce a constructor which includes all of these parameters. This could quickly lead to a huge constructor, potentially with a dozen or more parameters. Constructors like this are often very difficult to work with (I personally try to avoid any more than 3 parameters on any method/constructor). In these situations we may wish to abstract the complex construction logic into a separate ‘Factory’ class which tames the beastly constructor for us, but there is another way – The Fluent Build Context.
public interface ICarBuildContext
{
ICarBuildContext WithEngine(EngineBase engine);
ICarBuildContext WithDriversSeat(SeatBase seat);
ICarBuildContext WithPassengerSeat(SeatBase seat);
ICarBuildContext WithWheelType(WheelType wheelType);
ICarBuildContext WithBrakes(BrakeBase breakes);
Car Build();
}
This interface allows us to construct a Car using a very nice & readable method chain:
var myCar = carBuilder.WithEngine(new DieselEngine())
.WithDriversSeat(new LuxuryHeatedSeat())
.WithPassengerSeat(new BucketSeat())
.WithWheelType(WheelType.Alloy)
.WithBrakes(new CeramicBrakes())
.Build();
But how do we integrate this interface with our Car class? Through an often under-utilized feature of C# – Explicit Interface Implementation.
public class Car : ICarBuildContext
{
public EngineBase Engine { get; private set; }
public SeatBase DriversSeat { get; private set; }
public SeatBase PassengerSeat { get; private set; }
public WheelType Wheels { get; private set; }
public BreakeBase Brakes { get; private set; }
ICarBuildContext ICarBuildContext.WithEngine(EngineBase engine)
{
this.Engine = engine;
return this;
}
ICarBuildContext ICarBuildContext.WithDriversSeat(SeatBase seat)
{
this.DriversSeat = seat;
return this;
}
ICarBuildContext ICarBuildContext.WithPassengerSeat(SeatBase seat)
{
this.PassengerSeat = seat;
return this;
}
ICarBuildContext ICarBuildContext.WithWheelType(WheelType wheelType)
{
this.Wheels = wheelType;
}
ICarBuildContext ICarBuildContext.WithBrakes(BrakeBase brakes)
{
this.Brakes = brakes;
return this;
}
Car ICarBuildContext.Build()
{
return this;
}
}
By having Car explicitly implement the build context interface itself, we are able to set the private properties on the class – effectively allowing consumers to tunnel through the immutability of the class, but only on our terms. Once the Car has been constructed it becomes immutable again – the interface adapts by context.
The final step in the chain is to suppress the default constructor for Car and introduce a static factory method:
public class Car
{
private Car() { }
static public ICarBuildContext BeginBuild()
{
return new Car();
}
// ...
}
Usage:
var myCar = Car.BeginBuild()
.WithEngine(new V8Engine())
.WithDriversSeat(new RacingSeat())
// No passenger seat - this is a race car!
.WithWheelType(WheelType.Alloy)
.WithBrakes(new CeramicBrakes())
.Build();
In a future blog post I’ll go into detail about how this approach also gives us the ability to build in detailed construction validation into the process. I’ll also go into detail about how we can nest build contexts to build up a complex object graph in one method chain.
CodeProject
C# XML Documentation – Referencing code elements with the cref attribute.
XML documentation comments in C# are a great feature, especially if you’ve used an automatic documentation generator such as Sandcastle. Being able to generate documentation directly from source code is nothing new of course, Doxygen has been doing a great job of it since 1997, and I’m sure it wasn’t an original concept then.
But generating documentation from source code introduces its own set of considerations. Code comments are no longer private messages or dialog between developers, or even messages to your future self. Making the decision to generate documentation from source code requires a subtle shift in your mentality as you write your XML comments.
There are a set of seldom used and often misunderstood elements within the XML documentation syntax that can solve these issues, and a standardised way of referencing code elements. Elements such as <see> and <seealso> allow you to reference other code elements, and are commonly converted into clickable hyperlinks in the generated documentation file.
Take this simple method as an example:
public void AddComponent(IComponent component)
{
if (component == null)
throw new ArgumentNullException("component");
this.components.Add(component);
}
When it comes to commenting this method, the main point that needs to be documented is the fact that this method will reject a null reference passed to the component parameter, and in such cases will throw an ArgumentNullException.
Immediately we have an external element that we need to reference – ArgumentNullException, and we can accomplish this by adding an <exception> tag to the XML comments..
/// <summary>
/// Adds a component to the container.
/// </summary>
/// <exception cref="T:System.ArgumentNullException">
/// Thrown if the value passed to the <paramref="component"/> parameter is null.
/// </exception>
public void AddComponent(IComponent component)
{
if (component == null)
throw new ArgumentNullException("component");
this.components.Add(component);
}
The tag has a cref attribute which lets us reference an external code element. As you can see, the name of the external code element must be fully qualified. In addition to the fully qualified name of the external code element, which can also hint at what the code element is… ‘T:’ in this case means we are referring to a Type, but there are several other options:
Now, expanding our simple example method to raise an event, we can see that fully utilizing the ability to reference external code elements in the XML documentation makes for clearer, more precise generated documentation which is far easier to navigate. And as a bonus, if you’re using Sandcastle to generate your documentation, anywhere you use a type reference to a class in the Framework Class Library, your documentation will automatically contain a link to the relevent page on MSDN!
Final example:
/// <summary>
/// Adds a <see cref="T:System.ComponentModel.IComponent"/> instance to the container.
/// </summary>
/// <exception cref="T:System.ArgumentNullException">
/// Thrown if the value passed to the <paramref="component"/> parameter is null.
/// </exception>
/// <remarks>
/// Calling this method will raise the <see cref="E:MyNamespace.MyContainer.ComponentAdded"/> event.
/// </remarks>
public void AddComponent(IComponent component)
{
if (component == null)
throw new ArgumentNullException("component");
this.components.Add(component);
// Raise a component added event...
this.OnComponentAdded(component);
}