I noted some time ago that one can use delegates instead of interfaces that contain one method. In fact, I think delegates are more flexible when compared to single-method interfaces and I'll try to explain why.
The scenario
As a reminder, the discussion started with this post: Making ICollection inherit from more fine-granular interfaces. What was the problem? I wanted to design a method that adds a large amount of data to some collection. The method would possibly serve all sorts of different clients (callers). A very reusable solution would be to just return the items so that they can be added by the client (on the caller's side), but that would mean:
- Clients have to do additional work of adding the items to their collections (duplication of code with the same intent)
- This also costs at least double performance (each element is first put into a temporary storage, and then put into the target storage, which gives us O(2n))
- The method had to accumulate the items before returning them (O(n) memory consumption for the returned values (List<T>, ReadOnlyCollection<T> or whatever)
- Even in case of yield return, where O(1) temporary storage for return values is required (values are being returned lazily one-by-one as they are needed), you'd still have to add those values to the target collection, sooner or later
That's why I decided to go for a void method that accepts a collection where to add the items (to avoid performance penalties). My first implementation accepted the ICollection<T>, the .NET framework's most probable candidate for a thingy that has an 'Add' on it. However, ICollection<T> is not good enough, because not all the types that have 'Add' on it implement ICollection<T>. There was some buzz on the blogosphere about duck-typed implementation of collection initializers in C# 3.0 - a good example of a place where we definitely lack the ISupportsAdd<T> interface or something like this.
The Solution with Delegates
To make long story short, here's a what I think a good solution to the problem. Instead of having the interface:
interface ISupportsAdd<T>it is enough to use the System.Action<T> delegate. Compare the old way and the new way:
{
void Add(T item);
}
void OldAdd<T>(ISupportsAdd<T> collectionToFill)And on the caller's site:
{
collectionToFill.Add(item1);
// ...
collectionToFill.Add(item100);
}
void NewAdd(System.Action<T> addMethod)
{
addMethod(item1);
// ...
addMethod(item100);
}
// The target collection can be anything
// that has an "Add" method
// (which can even be named differently)
List<int> resultsToFill = new List<int>();
// Don't pass the entire collection
// when you are only going to use
// the Add method anyway!
// Only pass the Add method itself:
NewAdd(resultsToFill.Add);
See, that easy. Just pass the "pointer" to the Add method of your choice and the NewAdd method will call it as many times as there are items!
The important lesson I've learned from this: to make a piece of code reusable (in this case, a method), one should only provide as little information to it as it is necessary.
Earlier I tried to do that subconsciously by demanding more "fine-granular" interfaces - only to provide as little information about the type as necessary (we were not going to use Remove() anyway, so why provide it?).
Delegates - "interfaces on a member level"
Now I've realized, that in case where you only need to use one method, you can go even more granular - no need to pass the type, just pass a member (members are more fine-granular than types). See how it already starts to smell a little like duck-typing? You don't have to declare a necessary interface, your existing type signature is just fine, as long as you provide the necessary member. This is an awesome bonus feature in C#. Unfortunately, it is only limited to one member, and even more, that member has to be a method! (No delegate properties, pity, isn't it?)
Delegates vs. single-method interfaces
To summarize, there has been ongoing talk in the blogosphere about adding an implementation of an interface to an existing type without changing the type itself. There is a desire to automatically or manually make the type implement some interface, as soon as it has all necessary members with the right signatures. C# currently offers a solution, but only if your "interface" consists of exactly one method - you can then use a delegate to that method and pass it around. Delegates in this case are semantically equivalent to single-method interfaces - you can pass around both and you can call both. But delegates are more flexible, because you can take a delegate ("pointer") to an existing type (its method actually) without changing it, and you can't make an existing type implement your interface without changing it.
1 comment:
IComparer<T> is probably a good example where they should have gone with a delegate instead. What do you think?
Post a Comment