C# Extension Methods and Fluent Interfaces

February 7, 2013

one comment

The idea of fluent interfaces is not new, and has many forms. The basic idea is to use a single statement to encompass a series of operations that are natural, or at least simple, to use. There are very few fluent interfaces in .NET – the most well known, which has some fluent interface semantics, is the System.Text.StringBuilder class. Here’s a simple example:

public static string BuildInfo(Process process) {

    return new StringBuilder("Process ")

        .AppendLine(process.ProcessName)

        .Append("Started at ").AppendLine(process.StartTime.ToLongTimeString())

        .Append("Running with ").Append(process.Threads.Count).AppendLine(" threads.")

        .ToString();

}

This “chaining” is made possible by the fact that the Append and AppendLine methods of StringBuilder return the same StringBuilder instance (this).

StringBuilder is a simple example, let’s try to create something of our own. Here’s a simple Customer class with a fluent interface:

class Customer {

    List<Item> _items = new List<Item>();

 

    public string Name { get; private set; }

    public string AccountManager { get; set; }

 

    public Customer(string name) {

        Name = name;

    }

 

    public Customer WithAccountManager(string name) {

        AccountManager = name;

        return this;

    }

 

    public Customer AddItems(params Item[] items) {

        _items.AddRange(items);

        return this;

    }

}

Properties are not suited for a fluent interface because property setters cannot return anything, so chaining is impossible. This means we have to turn properties into methods for “fluency”. With the above example, we can write code like the following (Item is a some class with a constructor accepting a string):

 

var customer1 = new Customer("Bart Simpson")

    .WithAccountManager("Bernz")

    .AddItems(new Item("ball"), new Item("cactus"));

This is all good and well. Now let’s add a Customer-derived type like so:

class CorporateCustomer : Customer {

    public bool PremierSupport { get; set; }

 

    public CorporateCustomer(string name)

        : base(name) {

    }

 

    public CorporateCustomer WithPremierSupport(bool isPremier = true) {

        PremierSupport = isPremier;

        return this;

    }

}

Now let’s try to create a CorporateCustomer and initialize with the fluent interface:

var customer2 = new CorporateCustomer("Homer Simpson")

    .WithAccountManager("Bernz")

    .WithPremierSupport();

Unfortunately, the code fails to compile. Can you spot the problem?

The problem is that WithAccountManager returns a Customer and not a CorporateCustomer, so the compiler rejects the call to WithPremierSupport. How can we solve that?

We can try to make the WithAccountManager method generic, like so:

public TCustomer WithAccountManager<TCustomer>(string name) where TCustomer : Customer {

    AccountManager = name;

    return this as TCustomer;

}

In this case, the customer-creating code still does not compile. Can you spot the problem now, dear reader?

The problem is more subtle; in fact, the code that creates a plain Customer fails to compile as well. The problem is that the compiler cannot infer the generic argument automatically, because the input arguments to WithAccountManager have nothing to do with customer, and the return type can be anything that derives from customer – the compiler can’t simply select one – it does not understand the intent of the method – returning this as the “real” type of the object.

How can we solve that? One obvious way is to specify the generic arguments like so:

var customer1 = new Customer("Bart Simpson")

    .WithAccountManager<Customer>("Bernz")

    .AddItems(new Item("ball"), new Item("cactus"));

 

var customer2 = new CorporateCustomer("Homer Simpson")

    .WithAccountManager<CorporateCustomer>("Bernz")

    .WithPremierSupport();

This works, but using it is very tedious. We just want to get rid of the explicit generic argument and still return the correct type. The solution? Extension methods:

static class CustomerExtensions {

    public static TCustomer WithAccountManager<TCustomer>(this TCustomer customer, string name) 

        where TCustomer : Customer {

        customer.AccountManager = name;

        return customer;

    }

}

With this in place (and the original WithAccountManager method removed), we can simply write:

var customer1 = new Customer("Bart Simpson")

    .WithAccountManager("Bernz")

    .AddItems(new Item("ball"), new Item("cactus"));

 

var customer2 = new CorporateCustomer("Homer Simpson")

    .WithAccountManager("Bernz")

    .WithPremierSupport();

This works because the compiler can deduce the correct type because of the first argument to the extension method; and this will continue to work correctly even if new classes deriving from Customer or CorporateCustomer are created.

Don’t you just love extension methods?

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

one comment

  1. Lior IsraelFebruary 10, 2013 ב 08:28

    COOOOOOOOOOOOOOOOOOOL!!!!!!!

    Reply