Each time you write a parametrized method you must decide which are the best types to use for your parameters. If you are not careful, your choice could place unnecessary restrictions on the caller of the method that requires them to pass too specific of a type. When you are making this decision you should ask yourself the following question:
Is there an interface that encapsulates all the members that I need for the type of this parameter?
Here is the classic example of this problem:
class Example
{
public void Print(List<String> values)
{
foreach (String value in values)
Console.WriteLine(value);
}
}
This Print method was not written with the caller in mind – these kinds of methods are often written with a narrow focus on the needs of the developer who wrote the method. This developer is used to using a List<T> for any sequence of items so he naturally chooses it as his parameter type for this method. But is it the right choice?
Since this article is about choosing interfaces I imagine that you rightly guessed not. In order to choose the correct type for the argument you need to first find the least restrictive type you can that will allow you to accomplish everything you need to do to satisfy the requirements of the method.
Here is the process for finding the best type for your parameter. Start by looking at the implementation of the type you want to use. List the methods and properties you use from it. Next determine if it implement any interfaces. If it does, does any one interface provide all the members your method needs to function? If so, repeat this exercise but this time look at the implementation of the interface you have chosen. Does it inherit from another interface? Is it that interface that actually provides the members you need? Keep doing this until you have reached the highest level of abstraction that you can. Once you cannot go any further you have found the right type for your parameter.
In the case that your type does not implement any interfaces or the interfaces it does implement cannot be used as the parameter type (in other words the exercise in the previous paragraph failed) you will have to rely on a concrete type for your parameter. At this point you must repeat the exercise but do so using the inheritance hierarchy of types above your type.
After you have completed this exercise you can be confident that you have chosen the most abstract choice possible for your parameter and therefore you are providing maximum flexibility for the callers of your method.
Let’s apply this process to a new method that removes all instances of “1″ from the Text property of an ASP.NET textbox:
void removeOnes(TextBox textbox)
{
textbox.Text
= textbox.Text.Replace("1", "");
}
Here is the mental process that I would go through to choose the proper type for this method:
- This
removeOnesmethod uses just one member fromTextBoxand that isTextBox.Text. TextBoximplements two interfaces:IPostBackDataHandlerandIEditableTextControl.- Does any one interface provide all the members your method needs to function? Yes,
IEditableTextControldeclares aTextproperty. - Does
IEditableTextControlinherit from any other interface and if so does that interface provide aTextproperty? Yes,IEditableTextControlinherits fromITextControlwhich does provide aTextproperty. - Does
ITextControlinherit from any other interface and if so does that interface provide aTextproperty? No, this is as far as we can go.
We have just found the most abstract interface that we can possibly use and that means that it is the best type to use for our parameter:
void removeOnes(ITextControl textControl)
{
textControl.Text
= textControl.Text.Replace("1", "");
}
Now that we have changed the type of the parameter to ITextControl we have now given much more flexibility to the caller of this method. We have also improved the reusability of this method since it can now be used for any control that implements the ITextControl interface (e.g. TextBox, Label, Literal and many more).
Can anyone tell me what type should have been used for the first example? What if the implementation of the first example changed a bit (as I am sure that everyone was able to quickly identify the proper type for the first example).
public void Print(Listvalues) { for (Int32 i = 0; i < values.Count; i++) Console.WriteLine(values[i]); }
Can you list what members of List<T> are being used by Print now? With that in mind, what type would you choose for values?
5 Comments
The most abstract type for your interface cannot be represented in the C# type system. Unfortunately, C# is extremely restrictive.
I recommend reading the paper, The Essence of the Iterator Pattern to get an idea of the most abstract interface (Traversable) for your function. The type system needs higher-order polymorphism to implement this.
@Tony – Thank you for that reading suggestion – it looks very good! You make an excellent point that there are theoretical abstractions that we could have used here to solve this problem. Perhaps I should have been a bit clearer when I said “the most abstract interface that we can possibly use.” I meant something more along the lines of “the most abstract interface that we can possibly use without modifying the language we are using.”
The reason for this blog post is a failure in C#’s expressivity. The correct blog title should be “Choosy developers do not choose C#.”
Anders should have stuck to Delphi.
I’d probably (pretty standardly) write methods:
public void Do(IEnumerable source, Action action) { foreach (T item in source) { action(item); } }public void PrintToConsole(IEnumerable source) { Do(source, item => Console.WriteLine(item)); }You could make them extension methods if you’re into that.
Not sure what the best name for “Do” is? I think others have called it “ForEach”.
Generally anywhere that you’re just iterating something you should be using IEnumerable as your type.
Doh, I’ve been reading your blog posts newest-to-oldest. You’ve already blogged about this method: http://togaroga.com/2009/11/you-dont-always-need-a-lambda-expression/ :-)
One Trackback/Pingback
[...] unnecessary restrictions on the caller of the method that requires them to pass too specific of… [full post] Andrew Andrew Hare's Blog codebest-practicec#interfaces 0 0 0 [...]
Post a Comment