<

Creating a fluent interface for your .Net class

Published on
9,125 Points
2,725 Views
4 Endorsements
Last Modified:
Approved
Éric Moreau
Senior Consultant for .Net VB & C# developer (mostly for Windows Forms project type). Also a nominated as a Microsoft MVP from 2004 to 2017.
I have seen a presentation lately about unit testing. One of the aspect covered by the presentation was the use of fluent interface to ease the wording of the tests (and ease the reading at the same time).

Is that limited to unit testing? Of course not. What a great way to provide an API to many of our classes.

This article will show you how to create fluent interface in your own classes to use from anywhere you are using your classes.

What it is


Consider this first example:

Dim s As String = "Eric Moreau"
s = s.Trim()
s = s.ToUpper()
s = s.Replace("R", "z")

Open in new window


The readability of these statements is arguable at best and it is not very efficient (knowing that strings are immutable). It is also a bit too long to write. We don’t want to always break line and assign again.

Another way of writing the same as above, is this simple line:

Dim s As String = "Eric Moreau".Trim().ToUpper().Replace("R", "z")

Open in new window


This is an example of a fluent interface that you have surely used for years. The result of the first string is passed to the Trim method which is in turn passed to the ToUpper method to finally be passed to the Replace method before being returned to the variable.

If you have used LINQ, Entity Framework, NHibernate, most mocking tools, and many others, you have already used fluent interfaces maybe without explicitly knowing you were using them.

Fluent interface is normally implemented by using method cascading to relay the instruction context of a subsequent call (source: Wikipedia). A different way of explaining it is that it is a sequence of methods that can be chained one after the other.

Here is an example of a LINQ query that uses fluent interface (exposed by extension methods in this case):

Dim dir As New IO.DirectoryInfo("C:\Windows\System32")
Dim fileList = dir.GetFiles("*.*").
    Where(Function(f) f.Name.StartsWith("odbc")).
    OrderBy(Function(f) f.FullName)

Open in new window


The Where and the OrderBy methods chains on the result of the GetFiles method and it reads very fluently (even if I prefer the lambda format of C# over the one of VB but that’s another story).

Downloadable demo application


The downloadable demo solution of this month contains both VB and C#.

Figure 1: The demo application in action
The fluent interface demo application

Alternatives


Through the years, developers asked Microsoft to help them save a few keystrokes and make their code more readable. Here is a sample of what we are trying to improve here:

Dim emp As Employee = New Employee
emp.Name = "Eric Moreau"
emp.Dob = New DateTime(1970, 5, 28)
emp.HireDate = New DateTime(2007, 2, 1)
emp.Salary = 123456
emp.BossName = "Pointy-Haired Boss"

Open in new window


Two notable mentions (only 1 if you are doing C#) when comes to object declaration and initialization are still very valid.

Fluent interface is a lot more than just initialization as you have seen in the 2 examples above.

Alternative 1 – Object initializer


Starting with .Net Framework 3.5 (Visual Studio 2008), after many years of C# developers (with many coming from the VB world) complained about the missing With statement, Microsoft released the object initializer as you can see in this example:

Dim emp2 As Employee = New Employee With {
    .Name = "Eric Moreau",
    .Dob = New DateTime(1970, 5, 28),
    .HireDate = New DateTime(2007, 2, 1),
    .Salary = 123456,
    .BossName = "Pointy-Haired Boss"
}

Open in new window


This syntax really helps shorten the initialization (and is of great help with collections) but won’t let you call methods!

Alternative 2 – With … End With (VB only)


This VB-only syntax (that C# developers complained about) has been existing for years. Even before .Net. I have done VB since its version 4 (yes, I am that old) and I remember it was there.

The idea is to surround the statements as shown here:

Dim emp3 As Employee = New Employee
With emp3
    .Name = "Eric Moreau"
    .Dob = New DateTime(1970, 5, 28)
    .HireDate = New DateTime(2007, 2, 1)
    .Salary = 123456
    .BossName = "Pointy-Haired Boss"
End With

Open in new window


Notice that this syntax is not only for object initialization has it works with both properties and methods.

Back to Fluent interface


So how can we achieve something that would work with both properties and methods to get a fluent interface?

There won’t be a compiler trick or special keywords. You will need to write some methods in your classes to provide this feature. How much code? Well at least one method for everything you want to chain. It won’t be complex code. The syntax I prefer also requires an interface.

Basic class


Look at this very simplified class just for the demo purpose:

Public Class Employee

    Public Name As String
    Public Dob As Date
    Public HireDate As Date
    Public Salary As Int32
    Public BossName As String

    Public Overrides Function ToString() As String
        Return String.Format(" The employee {0} was hired {1:D} with a salary of {2:c} reports to {3}. He was born on {4:D}", Name, HireDate, Salary, BossName, DOB)
    End Function

End Class

Open in new window


The ToString override is simply to ease the debugging.

Let’s say we want to “fluentize” it.

“Fluentize” the class


The way I prefer (because if you dig, you might find other) requires an interface like this one

Public Interface IEmployeeFluent

    Function OfName(ByVal pName As String) As IEmployeeFluent
    Function AsOf(ByVal pDate As Date) As IEmployeeFluent
    Function BornOn(ByVal pDate As Date) As IEmployeeFluent
    Function SetSalary(ByVal pSalary As Int32) As IEmployeeFluent
    Function ReportTo(ByVal pName As String) As IEmployeeFluent

End Interface

Open in new window


In the previous section, my class was showing 5 properties. My interface here shows 5 methods, one for each property because I have decided so. It doesn’t have to be a 1-for-1 relation but it often makes sense.

Now that we have our interface, we need to implement it in the base class.

This is how I have implemented the interface for my class:

Public Function OfName(ByVal pName As String) As IEmployeeFluent Implements IEmployeeFluent.OfName
	Name = pName
	Return Me
End Function

Public Function AsOf(ByVal pDate As Date) As IEmployeeFluent Implements IEmployeeFluent.AsOf
	HireDate = pDate
	Return Me
End Function

Public Function BornOn(ByVal pDate As Date) As IEmployeeFluent Implements IEmployeeFluent.BornOn
	DOB = pDate
	Return Me
End Function

Public Function SetSalary(ByVal pSalary As Integer) As IEmployeeFluent Implements IEmployeeFluent.SetSalary
	Salary = pSalary
	Return Me
End Function

Public Function ReportTo(ByVal pName As String) As IEmployeeFluent Implements IEmployeeFluent.ReportTo
	BossName = pName
	Return Me
End Function

Open in new window


Simple no? The trick when we want to have a fluent interface is that each method will return the object on which it is working. It affects a property and returned the current instance. We can now use our new fluent object with something like this:

Dim emp2 As IEmployeeFluent = New Employee()
MessageBox.Show(emp2.
    OfName("Eric Moreau").
    AsOf(New DateTime(2007, 2, 1)).
    BornOn(New DateTime(1970, 5, 28)).
    SetSalary(123456).
    ReportTo("Pointy-Haired Boss").
    ToString())

Open in new window


But usually, when we have a fluent interface, we don’t have to create an instance. Something in the chain will create one for us if we need to. This is why I have created a Shared method (static in C#) to return an instance of a new employee:

Public Shared Function Hire() As IEmployeeFluent
	Return New Employee
End Function

Open in new window


Now that I have this Hire method, I don’t have to create an instance prior to call my chain of operations. Code will look like this:

Dim emp As IEmployeeFluent = Employee.Hire().
    OfName("Eric Moreau").
    AsOf(New DateTime(2007, 2, 1)).
    BornOn(New DateTime(1970, 5, 28)).
    SetSalary(123456).
    ReportTo("Pointy-Haired Boss")

Open in new window


You can chain as many methods as you want as long as they are all returning an instance of your object.

The one exception is the last one. In a previous example, I have used MessageBox to show the employee data using the ToString method. This last method does not return an instance of an employee and we don’t really care as it is the last operation on the chain.

Debugging


Debugging can be harder as you now have a single line of code instead of multiple calls.

If you are the owner of the class, you can set a breakpoint in the fluent method and you will stop exactly where you want.

If you don’t have the code of that object, you will need to return to non-fluent method calls just for the sake of finding the issue.

Conclusion


As you have seen, adding a fluent interface to your existing classes is not a big deal but can makes your code a lot easier to read.

Fluent interface allows you to modify the API of your existing class with a more descriptive interface which improves its usability. At the same time, this preserves the current API interface and eliminates the risk of introducing bugs to code that is already implemented.
emoreau-DemoFluentInterface.zip
4
Comment
1 Comment
LVL 71

Author Comment

by:Éric Moreau
Done.
0

Featured Post

Cloud Class® Course: MCSA MCSE Windows Server 2012

This course teaches how to install and configure Windows Server 2012 R2.  It is the first step on your path to becoming a Microsoft Certified Solutions Expert (MCSE).

Join & Write a Comment

A query can call a function, and a function can call Excel, even though we are in Access. This is Part 2, and steps you through the VBA that "wraps" Excel functionality so we can use its worksheet functions in Access. The declaration statement de…
This is Part-2 of Learning to use the Power of Mailwasher Pro so if you haven't watched Part-1 yet, I urge you to do so before watching this video. Click this link to watch Part-1 (https://www.experts-exchange.com/videos/56638/Learn-to-use-the-POWER…

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month