Adding a “With” extension method mimicking F#
This is the first entry of a blog series about how functional programming, and the F# training I recently went to, gives me inspiration in my day-to-day work in C#. The first one will show how you can build a “with”-like feature in C#.
One of the many features of F# that stroke me during Robert Pickering’s “Beginning F#” course, was the Record Type and its ease of use. For those of you that don’t know F#, a record type can be compared with a Tuple whose properties would be named.
For instance, this F# snippet defines a Record Type Person with two properties Name and Age.
type Person = { Name: string; Town: string; Country: string; Age: int }
With this type defined, it is now possible to define an instance of that type :
let someone = { Name = "Luc"; Town = "Paris"; Country = "France"; Age = 29 }
A very handy feature of F# lets us now write this :
let someoneElse = { someone with Age = 30 }
The “with” keyword allow us to define “someoneElse” as a copy of “someone”, stating only the values of the properties to change.
In a project I have been working on recently, I had exactly the same need of defining a new instance with very little changes, but in C#. In order to do this, I wrote a simple extension method, base on the ICloneable interface :
public static class ClonableExtensions { public static T With<T>(this T original, Action<T> withAction) where T : ICloneable { T copy = (T)original.Clone(); withAction(copy); return copy; } }
With this extension method, if we define a class Person that implements the ICloneable interface, like this :
public class Person : ICloneable { public string Name { get; set; } public string Town { get; set; } public string Country { get; set; } public int Age { get; set; } public object Clone() { return new Person() { Name = this.Name, Town = this.Town, Country = this.Country, Age = this.Age }; } }
It is now possible to use the generic With extension method to write the following code :
Person someone = new Person() { Name = "Luc", Town = "Paris", Country = "France", Age = 29 }; Person someoneElse = someone.With(p => p.Age = 30);
I find this construct quite expressive and readable, but please note that there are several drawbacks :
- The C# objet has to be mutable. Its “Age” property is set after the object is created by the Clone method. Even though the Clone method could have used a copy constructor instead of setting properties, the generic With method must overwrite the properties values. Addendum : this problem is now solved in “Revisiting the With extension method”.
- The syntax to use in order to modify more than one property is less readable, because a lambda expression with multiple instructions has to be written between accolades.
- In the end, whatever you do, someoneElse will be 30, and his name is Luc.