Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Building Domain Specific Languages in C#, Part 2

Building fluent programmatic interfaces lets users make calls in natural ways similar to speech.


advertisement
he first installment of this article showed how to leverage C# syntax to create a specific type of domain specific language in C# called a "fluent interface." Building a fluent interface involves converting a complex API into something with a fighting chance of readability. This follow-up article shows you how to take advantage of some of C#'s cool new features to push the envelope on this style of coding.

Using Extension Methods for Additional Fluency

The previous article described a simple way for a .NET bakery to keep track of discount programs for their customers. Using the same "bakery" theme, you can create a fluent interface to allow the baker to keep track of recipes. Here's an example of the type of unit test needed to succeed:

var expected = new SimpleIngredient("Flour"); expected.Quantity = 42; var actual = 42.grams().of("Flour"); Assert.AreEqual( expected.Name, actual.Name); Assert.AreEqual( expected.Quantity, actual.Quantity);

The interesting code here is the line that includes:

42.grams().of("Flour");

Wow, that's really fluent. But how can it possibly compile and run? To find out, you need to take a little diversion to extension methods.

Simple Extension Methods

One new feature in C# 3.5 is the extension method, added to allow LINQ to do its magic. An extension method lets you create a class that extends the functionality of an existing class without subclassing. The extension methods appear as class methods. Here's a simple example. Suppose you'd really like to have a WordCount method on the String class—of course, String is sealed, so you can't subclass it to include custom methods. Fortunately, extension methods let you create a new method that appears directly on the String class.



public static class StringExtension { public static int WordCount( this String str) { return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries) .Length; } }

Eric Evans and Martin Fowler coined the term "fluent interface," and Martin popularized it in his blog in December of 2005. Since then, it has become the accepted term for a certain type of DSL.
Extension methods must appear in a static class definition, and the methods themselves must have static access. The extension method must take at least one parameter—the type of class extended. Notice in the WordCount method, the first (and, in this case, only) parameter is of type String. To enable the extension method, you need merely place a using directive at the top of the file that includes your extension classes. In this case, any class that includes using StringExtension at the top of the file may call the WordCount method on any string.

Extension methods exist because LINQ needs them to perform its syntactic sugar magic. But, of course, when new features appear, developers can use them for whatever purpose arises, such as recipes.

Recipe Extensions

Extension methods work for all classes, both built-in library classes and your own classes. Domain specific language fluent interfaces frequently need quantities of things: "2 weeks," "5 pounds," "12 months," "6.2 miles." Therefore, to create truly fluent interfaces, you need to be able to use numbers to specify quantities (such as 42.grams()). For such constructs to work, you must add a new method to the int "class." But, of course, you can't: int is one of the built-in system types that represent primitives. However, auto-boxing works just fine for constant primitives (such as 42). That means that you can add an extension method to the Int32 class—which the compiler will use when it auto-boxes the primitive 42 into a class. The following code shows the extension class specifying the gram method:

public static class RecipeExtensions { public static int Gram( this int weight) { return weight; } }

As required, RecipeExtensions is a static class, which includes a static method called gram. The gram method accepts a single parameter (of type int, which is auto-boxed up to an Int32) and simply returns the value. Why return just weight? For the recipe DSL, all weights are expressed in grams. Because this is a gram method, it simply returns the number of grams. Later you'll see how to create a pound method that does some conversions.

This next snippet shows the unit test demonstrating the new extension method:

[Test] public void gram_extension_for_integer() { Assert.AreEqual(3, 3.gram()); Assert.AreNotEqual(4, 3.gram()); }

C# handles the syntactic sugar that lets you call a new extension method on a constant integer, creating a fluent interface that uses actual constants for quantities.

Editor's Note: This article was first published in the January/February 2009 issue of CoDe Magazine, and is reprinted here by permission.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap