Constrained generic extension methods
This post was originally published here.
When I first saw extension methods, a new feature in C# 3.0, I was a bit skeptical. It seemed like yet another language feature shoehorned in to support LINQ. After going a few rounds with the technology, it actually looks quite promising. For a full explanation of extension methods and what scenarios they can enable, check out Scott Guthrie’s post on the subject.
Making the extension method generic
First, let’s examine the original example from Scott’s post, which adds an “In” method to test whether an item is in a collection:
public static bool In(this object o, IEnumerable items) { foreach (object item in items) { if (item.Equals(o)) return true; } return false; }
The “In” method will be added to all objects, including primitive value types such as “int” and “double”. Here’s a sample snippet using “int”:
int[] values = {5, 6, 10}; bool isInArray = 7.In(values);
The value “isInArray” evaluates to “false”, as the number 7 isn’t in the array. So what’s wrong with this code? The problem is that the “In” method uses the type “object” to extend types, but that will cause boxing when the number 7 is boxed to an object for the extension method call. Generics can prevent boxing from occurring, so let’s change the extension method to be generic:
public static bool In<T>(this T o, IEnumerable<T> items) { foreach (T item in items) { if (item.Equals(o)) return true; } return false; }
Now when I call “In”, the method will use “int” instead of “object”, and no boxing will occur. Additionally, since I used the generic IEnumerable
string[] values = {"5", "6", "10"}; bool isInArray = 7.In(values); // Compile time error!
I can now lean on the compiler to do some type checking for me. What about some more interesting scenarios, where I want to extend some complex types?
Adding constraints to a generic extension method
I’d like to add some complex comparison operators to certain types, say something like “IsLessThanOrEqualTo”. I’d like to extend types that implement both IComparable
public static bool IsLessThanOrEqualTo<T>(this T lValue, T value) where T : IComparable<T>, IEquatable<T> { return lValue.CompareTo(value) < 0 || lValue.Equals(value); }
This extension method lets me write code such as:
int x = 5; int y = 10; bool isLte = x.IsLessThanOrEqualTo(y); Assert.IsTrue(isLte);
This gives me a few advantages:
- The available constraints are the same as any other generic type or method (struct, class, new(),
, , and naked type constraints) - Using multiple constraints lets me constrain the method to types that fulfill all the constraints, such as the two interfaces in the above example
- The signature of type T inside the generic method combines the members of all of the constraints.
- All of the methods of IComparable
and IEquatable are available to me in the above example. - The IDE filters the constraints in IntelliSense, and I won’t even see extension methods whose constraints my variable doesn’t fulfill</ul> What this allows me to do in real world situations is to target specific scenarios by filtering through constraints. Instead of using only 1 type in the “this” parameter, I can target objects that derive from a certain class, implement several interfaces, etc. By making the class generic, interactions with the method will be strongly typed and will avoid boxing conversions and unnecessary casting.
To see the constraints fail, what happens when we try to call the IsLessThanOrEqualTo with a class that only partially fulfills the constraints? Here’s the bare-bones class:
public class Account : IComparable<Account> { public int CompareTo(Account other) { return 0; } }
I try to compile the following code:
Account account = new Account(); Account other = new Account();
- All of the methods of IComparable
- The signature of type T inside the generic method combines the members of all of the constraints.
- Using multiple constraints lets me constrain the method to types that fulfill all the constraints, such as the two interfaces in the above example
account.IsLessThanOrEqualTo(other); // Compile time error!</pre> </div>
It won’t compile, since Account only implements IComparable<T>, and not IEquatable<T>. This wouldn’t happen while authoring the code, as IntelliSense doesn’t even show the IsLessThanOrEqualTo method.
I should note that if I don’t constrain the generic extension method, any instance of any type will have the method available for use, which may or may not be desirable.
#### Conclusion
Generic types and methods can be very helpful in eliminating boxing and unboxing as well as casting that clutters up the code. Extension methods allow me to add functionality to types that I may not have access to changing. By combining generic, constrained methods and extension methods, I get the power of extension methods with the flexibility of generic methods.