This post is the answer to yesterday’s brainteaser. As a reminder, I was asking what purpose this code might have:
{
public static void Add<T>(this ICollection<T> source, T item)
{
source.Add(item);
}
}
There are plenty of answers, varying from completely incorrect (sorry!) to pretty much spot on.
As many people noticed, ICollection<T> already has an Add method taking an item of type T. So what difference could this make? Well, consider LinkedList<T>, which implements ICollection<T>, used as below:
LinkedList<int> list = new LinkedList<int>();
list.Add(10);
That’s not valid code (normally)…. whereas this is:
ICollection<int> list = new LinkedList<int>();
list.Add(10);
The only difference is the compile-time type of the list variable – and that changes things because LinkedList<T> implements ICollection<T>.Add using explicit interface implementation. (Basically you’re encouraged to use AddFirst and AddLast instead, to make it clear which end you’re adding to. Add is equivalent to AddLast.)
Now consider the invalid code above, but with the brainteaser extension method in place. Now it’s a perfectly valid call to the extension method, which happens to delegate straight to the ICollection<T> implementation. Great! But why bother? Surely we can just cast list if we really want to:
((IList<int>)list).Add(10);
That’s ugly (really ugly) – but it does work. But what about situations where you can’t cast? They’re pretty rare, but they do exist. Case in point: collection initializers. This is where the C# 6 connection comes in. As of C# 6 (at least so far…) collection initializers have changed so that an appropriate Add extension method is also permitted. So for example:
LinkedList<int> list = new LinkedList<int> { 10, 20 };
That’s invalid in C# 5 whatever you do, and it’s only valid in C# 6 when you’ve got a suitable extension method in place, such as the one in yesterday’s post. There’s nothing to say the extension method has to be on ICollection<T>. While it might feel nice to be general, most implementations of ICollection<T> which use explicit interface implementation for ICollection<T>.Add do so for a very good reason. With the extension method in place, this is valid too…
ReadOnlyCollection<int> collection = new ReadOnlyCollection<int>(new[] { 10, 20 }) { 30, 40 };
That will compile, but it’s obviously not going to succeed at execution time. (It throws NotSupportedException.)
Conclusion
I don’t think I’d ever actually use the extension method I showed yesterday… but that’s not quite the same thing as it being useless, particularly when coupled with C# 6’s new-and-improved collection initializers. (The indexer functionality means you can use collection initializers with ConcurrentDictionary<,> even without extension methods, by the way.)
Explicit interface implementation is an interesting little corner to C# which is easy to forget about when you look at code – and which doesn’t play nicely with dynamic typing, as I’ve mentioned before.
And finally…
Around the same time as I posted the brainteaser yesterday, I also remarked on how unfortunate it was that StringBuilder didn’t implement IEnumerable<char>. It’s not that I really want to iterate over a StringBuilder… but if it implemented IEnumerable, I could use it with a collection initializer, having added some extension methods. This would have been wonderfully evil…
using System.Text;
public static class Extensions
{
public static void Add(this StringBuilder builder, string text)
{
builder.AppendLine(text);
}
public static void Add(this StringBuilder builder, string format, params object[] arguments)
{
builder.AppendFormat(format, arguments);
builder.AppendLine();
}
}
class Test
{
static void Main()
{
// Sadly invalid :(
var builder = new StringBuilder
{
"Just a plain message",
{ "A message with formatting, recorded at {0}", DateTime.Now }
};
}
}
Unfortunately it’s not to be. But watch this space – I’m sure I’ll find some nasty ways of abusing C# 6…
Why do you want to use a collection initializer with StringBuilder? The cool thing about StringBuilder is that its Append and AppendLine methods return the StringBuilder-instance itself, so you can write code like this:
var builder = new StringBuilder()
.AppendLine(“Just a plain message”)
.AppendFormat(“A message with formatting, recorded at {0}”, DateTime.Now)
.AppendLine();
Like you can see, unfortunately there’s no AppendLine-method that accepts multiple arguments for formatting, but it’s not hard to create an extension-method for that:
public static StringBuilder AppendLine(this StringBuilder builder, string format, params object[] args)
{
return builder
.AppendFormat(format, args)
.AppendLine();
}
LikeLike
@Tommy: Basically just because it would be a silly thing to do – and potentially make the code slightly simpler to read by removing the “Append” cruft.
LikeLike
Can you have then an extension method for StringBuilder such as Add(string) or Add(string,string)?
Will collection initializer use them?
LikeLike
@Alexander: You *can* have such extension methods, but collection initializers won’t work with StringBuilder because it doesn’t implement IEnumerable.
LikeLike
Extension method is something in which is purposed to extend of something.
In this usage case, it provides hooks in such way to create a pipeline for the existing Add() method life cycle.
This extension method, then, can be further extended in pattern which oriented to the open/closed principle.
LikeLike
I don’t think this has been mentioned yet, but if you are using that ICollection extension method, it lets you call `.Add()` on a plain array, which gives a runtime error of course.
LikeLike
@Matthew: That’s basically covered by the fact that arrays implement ICollection.Add explicitly – the whole point is “consider explicit interface implementation”.
LikeLike