This page shows the source for this entry, with WebCore formatting language tags and attributes highlighted.

Title

Creating fluent interfaces with inheritance in C#

Description

Fluent interfaces---or "method chaining" as it's also called---provide an elegant API for configuring objects. For example, the Quino query API provides methods to restrict (<c>Where</c> or <c>WhereEquals</c>), order (<c>OrderBy</c>), join (<c>Join</c>) and project (<c>Select</c>) data. The first version of this API was very traditional and applications typically contained code like the following: <code> var query = new Query(Person.Metadata); query.WhereEquals(Person.Fields.Name, "Müller"); query.WhereEquals(Person.Fields.FirstName, "Hans"); query.OrderBy(Person.Fields.LastName, SortDirection.Ascending); query.OrderBy(Person.Fields.FirstName, SortDirection.Ascending); var contactsTable = query.Join(Person.Relations.ContactInfo); contactsTable.Where(ContactInfo.Fields.Street, ExpressionOperator.EndsWithCI, "Strasse"); </code> <n>(This example gets all people named "Hans Müller" that live on a street with a name that ends in "Strasse" (case-insensitive) sorted by last name, then first name. <c>Fields</c> and <c>Relations</c> refer to constants generated from the Quino metadata model.)</n> <h>Fluent Examples</h> The syntax above is very declarative and relatively easy-to-follow, but is a bit wordy. It would be nice to be able to chain together all of these calls and remove the repeated references to <c>query</c>. The local variable <c>contactsTable</c> also seems kind of superfluous here (it is only used once). A fluent version of the query definition looks like this: <code> var query = new Query(Person.Metadata); query.WhereEquals(Person.Fields.Name, "Müller") .WhereEquals(Person.Fields.FirstName, "Hans") .OrderBy(Person.Fields.LastName, SortDirection.Ascending) .OrderBy(Person.Fields.FirstName, SortDirection.Ascending) .Join(Person.Relations.ContactInfo) .Where(ContactInfo.Fields.Street, ExpressionOperator.EndsWithCI, "Strasse"); </code> The example uses indenting to indicate that restriction after the join on the "ContactInfo" table applies to the "ContactInfo" table instead of to the "Person" table. The call to <c>Join</c> logically returns a reference to the joined table instead of the query itself. However, each such table also has a <c>Query</c> property that refers to the original query. Applications can use this to "jump" back up and apply more joins, as shown in the example below where the query only returns a person if he or she also works in the London office: <code> var query = new Query(Person.Metadata); query.WhereEquals(Person.Fields.Name, "Müller") .WhereEquals(Person.Fields.FirstName, "Hans") .OrderBy(Person.Fields.LastName, SortDirection.Ascending) .OrderBy(Person.Fields.FirstName, SortDirection.Ascending) .Join(Person.Relations.ContactInfo) .Where(ContactInfo.Fields.Street, ExpressionOperator.EndsWithCI, "Strasse").<hl>Query</hl> .Join(Person.Relations.Office) .WhereEquals(Office.Fields.Name, "London"); </code> A final example shows how even complex queries over multiple table levels can be chained together into one single call. The following example joins on the "ContactInfo" table to dig even deeper into the data by restricting to people whose web sites are owned by people with at least 10 years of experience: <code> var query = new Query(Person.Metadata); query.WhereEquals(Person.Fields.Name, "Müller") .WhereEquals(Person.Fields.FirstName, "Hans") .OrderBy(Person.Fields.LastName, SortDirection.Ascending) .OrderBy(Person.Fields.FirstName, SortDirection.Ascending) .Join(Person.Relations.ContactInfo) .Where(ContactInfo.Fields.Street, ExpressionOperator.EndsWithCI, "Strasse") .Join(ContactInfo.Relations.WebSite) .Join(WebSite.Relations.Owner) .Where(Owner.Fields.YearsExperience, ExpressionOperator.GreaterThan, 10).<hl>Query</hl> .Join(Person.Relations.Office) .WhereEquals(Office.Fields.Name, "London"); </code> This API might still be a bit too wordy for some (.NET 3.5 Linq would be less wordy), but it's refactoring-friendly and it's crystal-clear what's going on. <h>Implementation</h> When there's only one class involved, it's not that hard to conceive of how this API is implemented: each method just returns a reference to <c>this</c> when it has finished modifying the query. For example, the <c>WhereEquals</c> method would look like this: <code> IQuery WhereEquals(IMetaProperty prop, object value); { Where(CreateExpression(prop, value); return this; } </code> This isn't rocket science and the job is quickly done. However, what if things in the inheritance hierarchy aren't that simple? What if, for reasons known to the Quino framework architects, <c>IQuery</c> actually inherits from <c>IQueryCondition</c>, which defines all of the restriction and ordering operations. The <c>IQuery</c> provides projection and joining operations, which can easily just return <c>this</c>, but what type should the operations in <c>IQueryCondition</c> return? The problem area is indicated with question marks in the example below: <code> public interface IQueryCondition { <hl>???</hl> WhereEquals(IMetaProperty prop, object value); } public interface IQueryTable : IQueryCondition { IQueryTable Join(IMetaRelation relation); } public interface IQuery : IQueryTable { IQueryTable SelectDefaultForAllTables(); } </code> The <c>IQueryCondition</c> can't simply return <c>IQueryTable</c> because it might be used elsewhere<fn>, but it can't return <c>IQueryCondition</c> because then the table couldn't perform a join after a restriction because applying the restriction would have restricted the fluent interface to an <c>IQueryCondition</c> instead of an <c>IQueryTable</c>. The solution is to make <c>IQueryCondition</c> generic and pass it the type that it should return instead of hard-coding it. <code> public interface IQueryCondition<hl><tself></hl> { <hl>TSelf</hl> WhereEquals(IMetaProperty prop, object value); } public interface IQueryTable : IQueryCondition<hl><iquerytable></hl> { IQueryTable Join(IMetaRelation relation); } public interface IQuery : IQueryTable { IQueryTable SelectDefaultForAllTables(); } </code> That takes care of the interfaces, on to the implementation. The standard implementation runs into a small problem when returning the generic type: <code> public class QueryCondition<tself> : IQueryCondition<tself> { TSelf WhereEquals(IMetaProperty prop, object value) { // Apply restriction return <hl>(TSelf)this</hl>; // causes a compile error } } public class QueryTable : QueryCondition<iquerytable>, IQueryTable { IQueryTable Join(IMetaRelation relation) { // Perform the join return result; } } public class Query : IQuery { IQueryTable SelectDefaultForAllTables() { // Perform the select return this; } } </code> One simple solution to the problem is to cast down to <c>object</c> and back up to <c>TSelf</c>, but this is pretty bad practice as it short-circuits the static checker in the compiler and defers the problem to a potential runtime one. <code> public class QueryCondition<tself> : IQueryCondition<tself> { TSelf WhereEquals(IMetaProperty prop, object value) { // Apply restriction return (TSelf)<hl>(object)</hl>this; } } </code> In this case, it's guaranteed by the implementation that <c>this</c> is compliant with <c>TSelf</c>, but it would be even better to solve the problem without resorting to the double-cast above. As it turns out, there is a simple and quite elegant solution, using an abstract method called <c>ThisAsTSelf</c>, as illustrated below: <code> public abstract class QueryCondition<tself> : IQueryCondition<tself> { TSelf WhereEquals(IMetaProperty prop, object value) { // Apply restriction return <hl>ThisAsTSelf();</hl> } protected abstract TSelf ThisAsTSelf(); } public class Query : IQuery { protected override TSelf ThisAsTSelf() { return <hl>this</hl>; } } </code> The compiler is now happy without a single cast at all because <c>Query</c> returns <c>this</c>, which the compiler knows conforms to <c>TSelf</c>. The power of a fluent API is now at your disposal without restricting inheritance hierarchies or making end-runs around the compiler. Naturally, the concept extends to multiple levels of inheritance (e.g. if all calls had to return <c>IQuery</c> instead of <c>IQueryTable</c>), but it gets much uglier, as it requires nested generic types in the return types, which makes it much more difficult to understand. With a single level, as in the example above, the complexity is still relatively low and the resulting API is very powerful. <hr> <ft>And, in Quino, it is used elsewhere, for the <c>IQueryJoinCondition</c>.</ft>