UPDATE: A newer, step-by-step tutorial is now available at: https://www.scottlilly.com/how-to-create-a-fluent-interface-in-c
I recently wanted to make some functions a little easier to read. So I changed it around to use a fluent interface.
In case you aren’t familiar, a fluent interface generally takes something that isn’t very clear, and often needs a lot of setup code, and lets you chain methods in a way that (mostly) looks like a natural language sentence.
For instance, building a SQLCommand object and populating it with parameters. Normally, this might look like:
public void DoSomething() { SqlCommand command = new SqlCommand(); command.CommandType = CommandType.Text; command.CommandText = "SELECT * FROM [Customer] WHERE [City] = @City AND [State] = @State"; command.CommandTimeout = 30; command.Parameters.Add(new SqlParameter("@City", "Houston")); command.Parameters.Add(new SqlParameter("@State", "TX")); }
That code isn’t too difficult, but there are several things you need to remember. You could wrap all that behind a method, but having that method take several parameters starts to get messy.
Here’s what it looks like using a more fluent interface:
public void BuildAQuery() { SqlCommand command = SQLQueryFactory.ForQuery("SELECT * FROM [Customer] WHERE [City] = @City AND [State] = @State") .UsingParameter("@City", "Houston") .UsingParameter("@State", "TX") .BuildSqlCommandObject(); }
The code is easy enough to read that a non-technical person may get the basic idea of what is happening.
How do you build a fluent interface?
Here’s how I did it.
First, I created the SQLQueryFactory class, with the ForQuery() method. It takes the base SQL query, uses it to create a SQLQuery object, and returns that object.
namespace FluentInterface { public static class SQLQueryFactory { public static SQLQuery ForQuery(string query) { return new SQLQuery(query); } } }
One of the methods on the SQLQuery object is UsingParameter(), which adds the passed parameters to a collection. Since SQLQueryFactory’s ForQuery method returns a SQLQuery object, we can immediately call its UsingParameter method.
using System.Collections.Generic; using System.Data; using System.Data.SqlClient; namespace FluentInterface { public class SQLQuery { private readonly string _query; private readonly SortedList<string, object> _parameters; public SQLQuery(string query) { _query = query; _parameters = new SortedList<string, object>(); } public SQLQuery UsingParameter(string parameter, object value) { _parameters.Add(parameter, value); return this; } public SqlCommand BuildSqlCommandObject() { SqlCommand command = new SqlCommand(); command.CommandType = CommandType.Text; command.CommandText = _query; command.CommandTimeout = 30; foreach(KeyValuePair<string, object> parameter in _parameters) { command.Parameters.Add(new SqlParameter(parameter.Key, parameter.Value)); } return command; } } }
How can we chain the method multiple times?
Here’s where the secret sauce is.
Normally, you might expect to see a UsingParameter method that doesn’t return anything. However, in this case, it returns “this” – the whole SQLQuery object.
This lets you call any method of the SQLQuery object (for instance, the UsingParameter method again).
As long as your method returns the whole object, you can keep chaining calls to other methods on the object.
Eventually, the SQLQuery object’s BuildSqlCommandObject is called, returning a SqlCommand object – and ending the chain.
Why do this?
I often end of being “the framework guy”, in projects.
When I start building some framework code, one thing I watch out for is that it can easily be used by another programmer. I’ve seen lots of times where someone has a really good idea, but it isn’t clear to the other programmers who didn’t happen to come up with the idea – and don’t know everything that’s “obvious” to the creator of the source code.
Using a fluent interface lets you use a single object, with IntelliSense, which is better than assuming every other developer is going to know the required steps that need to happen before using your class.
1 Comment