But first - if you don't know it already - I suggest you read what an extension method is - really I could not explain it any better. Oh, and to all readers that want to jump the "stop the dotnetification of Delphi - keep these new language features away" wagon - this post is not for you, sorry.
Ever had a class or a set of classes you wanted to add some functionality to? Sure, there are ways to do so like the decorator pattern. But did you see the problem there if you have a type you cannot inherit from because either you cannot modify the code or it's not a class but an interface? Well, then create a new interface and add that functionality there, someone might say. How, if you cannot extend the given type? Use the adapter or bridge pattern? You can see where this is going. You might end having to change existing code or introduce lots of code to apply your additional functionality.
The most prominent example of extension methods (and not surprisingly the reason they were introduced in C# 3.0) are the extension methods for IEnumerable<T>. If you want to use the foreach (or for..in loop in Delphi) all you have to implement is the GetEnumerator method (and actually the only method that IEnumerable<T> got). So if you ever need to implement that in some of your classes you implement just one method and got access to almost any query operation you can imagine - not saying they all make sense in every context, but you get the idea.
Extension methods are great. You don't clutter your class with things that don't belong there directly but apply to an aspect of your class (in our case being enumerable). They follow good principles like the dependency inversion principle. The way you are using them is more natural and makes more sense than having static methods (or routines) where you pass in the instance you want to call the method on as first parameter.
Even without the fancy LINQ Syntax without question it is much more readable to write
for c in customers.Where(HasBillsToPay).OrderBy<string>(GetCompanyName) do
Writeln(c.CompanyName);
instead of
for c in EnumerableHelper.OrderBy<string>(
EnumerableHelper.Where(customers, HasBillsToPay), GetCompanyName) do
Writeln(c.CompanyName);
And that statement has only two chained calls - imagine how that grows in length if you got a more complex query with grouping or something else. Also in that case it is the order on how the query gets processed - easier to read and to write.
But - you remember - no helpers for interfaces and generics! Well, we can implement these methods in our base TEnumerable<T> class and/or put it on our IEnumerable<T> interface, no? Yes, we can. And everything would be fine if there wasn't one tiny detail - how generics are implemented in Delphi and how the compiler handles them: it generates a type for every specialization. Which means the same code compiled for every possible T in your application, for TCustomer, TOrder, TCategory and so on. Only with a small set of methods implemented (and possible classes for more complex operations like GroupBy for example) this means you get hundreds of KB added for each TList<T> you will ever use - even if you never touch these methods. That is because the linker cannot remove any method inside an interface even if never called.
So how to work around that problem (which is what I have been doing in the Spring4D refactoring branch lately)? Let's take a look again on how extension methods are defined. Nothing prevents us from creating that syntax in Delphi, so a Where method could look like this:
type
Enumerable = record
class function Where<TSource>(source: IEnumerable<T>;
predicate: TPredicate<TSource>): IEnumerable<T>;
end;
Simple, isn't it? But how to call it? We need a little trick here, let's see:
type
Enumerable<T> = record
private
fThis: IEnumerable<T>;
public
function GetEnumerator: IEnumerator<T>;
function Where(predicate: TPredicate<TSource>): Enumerable<T>
class operator Implicit(const value: IEnumerable<T>): Enumerable<T>;
end;
As you can see we use a record type that wraps the interface we want to extend and add the method there. We can implement the "extension methods" there or direct the call to our extension method type if we want to keep it seperatly.
We now have a nice way to do our query just like we wrote it above if customers where from Enumerable<T>. Or we can perform a cast (since we have an implicit operator that will get used). Also notice how the result of the Where method is of the record type. That way we can chain the calls easily. And because we implemented GetEnumerator we can use it in a for..in loop just like any IEnumerable<T>.
What's also nice about the record type is that the linker can now be smart and remove any method that we never call and save us dozens of megabytes in our binary (not kidding).
So our life could be so much easier if we had extension methods (or call them helper) for interfaces and generic types. But as long as we don't have that, we have to find some clever workarounds.
If you are a Spring4D user, check out the changes in the refactoring branch and let me know what you think.