Partager via


Another C# trick: Fluents, Inheritance and Extension Methods

Here’s a scenario which Damien and I encountered recently.

We needed a way of specifying Facets for properties on Entities, i.e. things like Nullable, MaxLength, Precision etc.

And we needed a class hierarchy because different types of properties have different sets of available facets.

The base class of the hierarchy is called PropertyConfiguration which defines a series of Fluent methods that are shared by all sub types:

public abstract class PropertyConfiguration
{
private bool _nullable;
public virtual PropertyConfiguration Nullable()
{
_nullable = true;
return this;
}
public virtual PropertyConfiguration NonNullable()
{
_nullable = false;
return this;
}
}

An example of a sub-type is StringPropertyConfiguration, which in addition to allowing you to specify Nullability() also allows you to set the MaxLength(..) of the string.

public class StringPropertyConfiguration: PropertyConfiguration
{
private int? _maxLength;
public StringPropertyConfiguration MaxLength(int maxLength)
{
_maxLength = maxLength;
return this;
}
}

Each of these fluent methods return this, so you can chain calls together like this:

var nameConfiguration = new StringPropertyConfiguration()
.MaxLength(100)
.Nullable();

This *looks* promising.

But there is a nasty limitation in this design.

If I change my code to this:

var nameConfiguration = new StringPropertyConfiguration()
     .Nullable()
.MaxLength(100);

It doesn’t compile.

If you look at the signature for PropertyConfiguration.Nullable() you can see it returns PropertyConfiguration (not StringPropertyConfiguration) so MaxLength(…) isn’t available.

Ouch.

Now we don’t want to subject our customers to these sort of arbitrary ordering restrictions.

So what do you do?

Well you could just do something like this:

public new StringPropertyConfiguration Nullable()
{
return base.Nullable() as StringPropertyConfiguration;
}

This works but is really annoying if you have a lot of effected methods, or a deep / wide inheritance hierarchy.

Thankfully Damien had a much better idea…

First replace the public fluent methods on PropertyConfiguration, with a good old fashioned property like this:

public abstract class PropertyConfiguration
{
private bool _nullable;
public bool Nullable {
get { return _nullable; }
set { _nullable = value; }
}
}

Then write an extension method that looks like this:

public static T Nullable<T>(this T propertyConfiguration)
where T: PropertyConfiguration
{
propertyConfiguration.Nullable = true;
return propertyConfiguration;
}

Now you can call Nullable() on any class that derives from PropertyConfiguration, and get by that class back rather than the base PropertyConfiguration class.

So with this in place you can write this:

var nameConfiguration = new StringPropertyConfiguration()
     .Nullable()
.MaxLength(100);

And all we did is write one property, and one generic extension method.

Nifty!

There is a meta-lesson buried in all this.

If you want to put Fluents on classes with an inheritance hierarchy, the easiest way is probably to write your classes the old fashion way (with non-fluent properties and methods) and when you’ve done that add a series of generically constrained extensions methods to add the required fluents.

Happy coding.

Comments

  • Anonymous
    July 31, 2009
    It's really interesting how extension methods are gradually accepted and integrated into the developer's toolbox.I like this in particular because it separates your hierarchy from the 'fluentness'. My guess is it will make your API more flexible.Thanks for sharing the tip!
  • Anonymous
    August 01, 2009
    Excellent idea!I wonder now why didn't I think of that earlier :)
  • Anonymous
    August 01, 2009
    Nice approach.The extension method as written doesn't compile.public static T Nullable<T>(this T propertyConfiguration)where T : PropertyConfiguration{ propertyConfiguration.Nullable = true; return propertyConfiguration;}
  • Anonymous
    August 01, 2009
    Very nice trick. Thanks. I love to work with fluent interfaces. I'm currently working on way to make my Safe.Lock (http://blog.decarufel.net/2009/06/how-to-implement-lock-with-timeout.html) working with fluent interface like this. I want to be able to start from this:Safe.Lock(obj, timeout, () => safeCode);to this:Safe.Lock(obj) .WithTimeout(timeout) .Do(()=> safecode);
  • Anonymous
    August 01, 2009
    @Doug,Thanks for the catch. I really need to learn to not rely on myself as a compiler when writing blog posts!Anyway thanks.Alex
  • Anonymous
    August 01, 2009
    Do you need a fluent API in this case when you're really setting a bunch of property values?var nameConfiguration = new StringPropertyConfiguration(){ Nullable = true, MaxLength = 100 }would be a lot easier and understand in this case, and takes a lot less code and trickery to implement - which builds upon maintainability.Further, the consumer of the API can go back and modify the object. In your example, setting nullable to not nullable would require another extension method NotNullable()) pretty soon we're no longer building a object hierarchy, but a collection of extension methods. Might as well use the dynamic keyword.I know this may be a poor example, but Fluent methods just for the sake of having fluent methods doesn't provide a benefit and actually can cause additional issues for API consumers when they are attempting to understand the API.If there is actual benefit in the chaining then having a fluent API is better in that situation (Eg, linq, decarufe's example.. etc.)
  • Anonymous
    August 01, 2009
    @Sean,Based on what I presented above, I'd agree too. But...the above example was re-written to simplify the scenario and make it easy to grok the central idea.In reality it works like this:var conf = new EntityConfiguration<Customer>();conf.ForProperty(c => c.Name).MaxLength(100).Nullable();Where depending upon the type of the lambda in ForProperty(..) a strongly typed PropertyConfiguration is constructed and registered for the user.I.e. the End user is not in charge of creating the StringPropertyConfiguration class so using member initializers is not an option.Does it seem more reasonable now?-Alex
  • Anonymous
    August 01, 2009
    You was able to do it with C#2 (so without extension method) like this:public abstract class PropertyConfiguration<T> where T : PropertyConfiguration<T>{   private bool _nullable;   public virtual T Nullable()   {       _nullable = true;       return (T)this;   }   public virtual T NonNullable()   {       _nullable = false;       return (T)this;   }}public class StringPropertyConfiguration : PropertyConfiguration<StringPropertyConfiguration>{   private int? _maxLength;   public StringPropertyConfiguration MaxLength(int maxLength)   {       _maxLength = maxLength;       return this;   }}Matthieu
  • Anonymous
    October 17, 2009
    This is exactly what I was looking for, and it works perfectly! Thanks for sharing this awesome trick on using generics to assist with creating a "generic" fluent interface for multiple classes that inherit from the same base class. Thanks!
  • Anonymous
    October 21, 2009
    Exactly what i was looking for.
  • Anonymous
    October 28, 2009
    The comment has been removed
  • Anonymous
    February 01, 2011
    for working the extention method, you have to add some static classes around that method
    public static class PropertyConfigurationExtensions{    public static T Nullable&lt;T&gt;(this T propertyConfiguration)     where T : PropertyConfiguration    {        propertyConfiguration.Nullable = true;        return propertyConfiguration;    }}