Saturday, August 18, 2007

Workarounds to Simulate Static Imports

Sometimes, it would be handy to write methods without preceeding them with an object or type. E.g. it would be nice to write

    Sin(x) / Cos(x)

instead of

    Math.Sin(x) / Math.Cos(x)

The shorter version is not possible in C#, so this post outlines all the workarounds I know of.


In Java, and some other languages, you can omit "Math." by doing a "static import" of the "Math" class. I asked Eric Gunnerson (ex C# Progamme Manager at MS) why static imports are not allowed in C#. He responded here, and I paraphrased his response as follows:

"Regardless of whether you see

> ...Color.Red

you always know that Red is something defined outside of the class you're current reading.

Compare that to


In the latter you have lost the clear hint that Sin is "outside the current class". It could be a method on the class you're reading, or it might not. So static class imports create a bit more ambiguity than namespace imports, which I think was Eric's point."

Nevertheless, something like static imports can be useful for DSL-like solutions. Anders Noras gives a good example (and an interesting discussion) here.

His sample Java code looks like this:

Dim doc As XmlDocument
doc = Document( _
Element("Employees", _
Element("Employee"), _
Comment("Highly valued employee..."), _
Element("Id", 42), _
Element("Name", "Arthur Dent"), _
Element("ContactInfo", _
Element("PhoneNumber", "555-5555"), _
Element("Address", "Upda road 42"), _
Element("Country", "England"))))

Here are all the ways I know of to simulate that kind of API in C#:

1. Simple ugly static methods

XmlDocument doc = DocHelper.Document(
DocHelper.Element("Employee"), _
DocHelper.Comment("Highly valued employee..."),
DocHelper.Element("Id", 42),
DocHelper.Element("Name", "Arthur Dent"),
DocHelper.Element("PhoneNumber", "555-5555"),
DocHelper.Element("Address", "Upda road 42"),
DocHelper.Element("Country", "England"))));


2. Make the thing before the dot look nicer.

2a. Creative naming of static classes

Sometimes you can do this with creative naming of static classes. For instance, instead of writing "CommonFunctions.EnsureNotMissing(...)" name the static class "Ensure" and write "Ensure.NotMissing(...)".

2b. Use instance methods, so you just have a single letter before the dot:

DocHelper a = new DocHelper();
XmlDocument doc = a.Document(
a.Element("Employee"), _
a.Comment("Highly valued employee..."),
a.Element("Id", 42),
a.Element("Name", "Arthur Dent"),
a.Element("PhoneNumber", "555-5555"),
a.Element("Address", "Upda road 42"),
a.Element("Country", "England"))));

3. Use constructors instead.

In this option, you write "new Element(...)" instead of "a.Element(...)"

4. Chain methods together "fluent" style

The downside of this is that you have to change your methods to return objects that support the next methods that you may call. While clever, this adds a level of complexity and so should be used with care. In general, I think there are better options.

XmlDocument doc = DocHelper.Document()
.Element("Employee"), _
.Comment("Highly valued employee..."),
.Element("Id", 42),
.Element("Name", "Arthur Dent"),
.Element("PhoneNumber", "555-5555"),
.Element("Address", "Upda road 42"),
.Element("Country", "England")));

5. Define methods in a base class.

This gives the desired syntax, but limits usage to derived classes. Depending on your particular needs, this may be a show-stopper.

6. Define methods in a base class, call from special one-off subclasses

Consider Anders' example. We are trying to create an XML document. As long as we are willing to define a class that corresponds to that particular document instance, we can write this:

void SomeMethod()
XmlDocument doc = new OurCoolDocument();

private class OurCoolDocument: DocumentWithHelperMethods
public OurCoolDocument():base(
Element("Employee"), _
Comment("Highly valued employee..."),
Element("Id", 42),
Element("Name", "Arthur Dent"),
Element("PhoneNumber", "555-5555"),
Element("Address", "Upda road 42"),
Element("Country", "England")))

All we've done is moved the methods into a class named DocumentWithHelperMethods. We can call them directly, with no prefix, from the constructor of the derived class. The only constraint is that, to call them from inside "base(..)", we must make them static methods. You can see a full example of this approach in one of the unit tests here.

7. Put extension methods on "object"

I haven't tried this, but I presume that if you defined Element, Comment etc as extension methods on "object" then the desired syntax would work anywhere (needing only a "using" of the namespace of the extension class).  Ugly. (Update Feb 08: and it doesn't work anyway, since an explicit "this" would be required, which defeats the purpose).


I find that 2a often works well. Whenever the desired method name consists of more than one word, you can consider using the first word as a class name.

But there is no one-size fits all solution.

It's not Rocket Science

Finally, this has really just been a post about one simple thing: finding ways to make methods that can be used without prefixing them. As Anders Noras says, this feels like introducing new "keywords" into the language. While this can be useful (and might even be interesting) it's not necessarily a DSL.

It is not rocket science. We're just trying to use meaningful method names, with no prefixes. Years ago, when I moved from BASIC to Pascal, I was thrilled to discover that I could write my own functions that looked "just like the built-in ones". It seems ironic that it's now so much harder to write methods without prefixes!


Links to this post:

Create a Link

<< Home