34

Why does this generate a compiler error:

class X { public void Add(string str) { Console.WriteLine(str); } }

static class Program
{
    static void Main()
    {
        // error CS1922: Cannot initialize type 'X' with a collection initializer
        // because it does not implement 'System.Collections.IEnumerable'
        var x = new X { "string" };
    }
}

but this doesn’t:

class X : IEnumerable
{
    public void Add(string str) { Console.WriteLine(str); }
    IEnumerator IEnumerable.GetEnumerator()
    {
        // Try to blow up horribly!
        throw new NotImplementedException();
    }
}

static class Program
{
    static void Main()
    {
        // prints “string” and doesn’t throw
        var x = new X { "string" };
    }
}

What is the reason for restricting collection initializers — which are syntactic sugar for a call to an Add method — to classes that implement an interface which doesn’t have an Add method and which isn’t used?

Dirk Vollmar
  • 172,527
  • 53
  • 255
  • 316
Timwi
  • 65,159
  • 33
  • 165
  • 230
  • 4
    There are some things in C# I just dont get. This is one of them. Another is `foreach`. – leppie Sep 15 '10 at 10:13
  • 2
    @leppie: What don't you "get" about foreach? – Jon Skeet Sep 15 '10 at 10:29
  • 1
    @Jon Skeet: I think leppie refers to the fact that the compiler simply requires the presence of a method without requiring a specific interface which is sort of duck-typing behaviour in a strongly-typed language. – Dirk Vollmar Sep 15 '10 at 10:32
  • 1
    @0xA3: That's mostly explained by the lack of generics in C# 1. This duck typing allowed for strongly typed iterators using value types without boxing. I *suspect* it wouldn't be present in C# if it had generics to start with, although it's still used to avoid boxing the iterator when you use foreach over a `List`, for example. – Jon Skeet Sep 15 '10 at 10:35
  • @Jon Skeet: The fact that it does more than one expects. I do 'get' it, I just dont get why. But as you said, it could be to do with generics. – leppie Sep 15 '10 at 11:41
  • related: http://stackoverflow.com/questions/459652/why-do-c-sharp-collection-initializers-work-this-way – Zaid Masud Sep 10 '12 at 17:46

1 Answers1

29

An object initializer doesn't; a collection initializer does. It's so that it's applied to classes which really represent collections, rather than just arbitrary ones which have an Add method. I have to admit that every so often I've "implemented" IEnumerable explicitly, just to allow collection initializers - but thrown a NotImplementedException from GetEnumerator().

Note that early in C# 3's development, collection initializers had to implement ICollection<T>, but that was found to be too restrictive. Mads Torgersen blogged about this change, and the reason behind requiring IEnumerable, back in 2006.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Wouldn’t the same argument apply to the LINQ query syntax? Yet it doesn’t require any interfaces, and even allows the “iterator variable” to be a type name and “Where/Select” to be a static method... – Timwi Sep 15 '10 at 10:46
  • @Timwi: Sort of, although it's less likely that a query expression will actually compile against an arbitrary type than a collection initializer - I suspect that `Add` has more "other meanings" than `Where` and `Select` methods with appropriate parameter types. Also note that there *isn't* one appropriate interface which could have been applied to sanity-check LINQ queries - think about Reactive Extnesions, which doesn't have any common interface with LINQ to Objects... – Jon Skeet Sep 15 '10 at 10:51
  • 9
    Not a day goes by without me wishing .NET 2 was the first version... 90% of all the cruft in .NET seems to come from the existence of .NET 1 with its non-generic stuff... – Roman Starkov Sep 15 '10 at 10:55