Thursday, October 11, 2007

How to Improve Collection Initializers

I think I'm starting to traverse the Linq/Orcas circle of (software) life, because I finally hit something that I don't like and that I couldn't work around. So far, anyway - maybe someone out there can tell me where I'm going wrong.

 

You may know that in the Orcas version of C#, they've added support for initializing collections via the funky new object initialization syntax. You can read about it here. The issue that I'm running into has to do with the fact that I'd like to initialize a collection in an object initialization expression, but I'm using XLinq to do it, and as a result I'm getting a compiler error.

 

See, I can get this to work:

 

Person craig = new Person

{

  Name = "Craig",

  Age = 35

  Children =

  {

     new Person

     {

        Name = "Ellen",

        Age = 2.9

     },

     new Person

     {

        Name = "TBD",

        Age = -0.4

     }

  }

}

 

But because this is just a simple example and in my real code I don't know the collection items at compile time, what I really want to do something is like this:

 

Person craig = new Person

{

  Name = "Craig",

  Age = 35,

  Children =

  {

     GetProgeny("Craig Andera")

  }

}

 

Where the Person class is the obvious implementation, and GetProgeny returns IEnumerable<Person>. Without doing anything else, I get a compiler error saying that Collection<Person> does not contain an overload of Add that accepts an IEnumerable<Person>, which makes perfect sense given that the collection initialization syntax is just shorthand for a bunch of calls to Add.

 

The part that sucks is that I can't get this to compose with extension methods to get what I want. That is, I'd like it if I were able to define an extension method like this:

 

public static void Add(this ICollection<Person> collection, IEnumerable<Person> items)

{

   foreach (Person item in items)

   {

      collection.Add(item);

   }

}

 

But that doesn't work. I'm not sure why, but here's the list of things I hope it is, in decreasing order of desirability:

 


  1. I'm doing something wrong.

  2. It doesn't work and there's a good reason.

  3. It doesn't work and there's no good reason.

 

Anyone have any idea which it is?

 

Update: I should have said right up front that Children is a read-only property of Person. I always make my collections read-only, and would prefer to do so in this case as well.

7 comments:

  1. Not sure if I'm understanding this correctly, but I don't see a need for an extension method. If you remove the braces around GetProgeny() it should work fine.



    var derek = new Person { Name = "Derek", Age = 27, Children = GetProgeny("Derek") };

    ReplyDelete
  2. In order to do that, I'd have to make my collection read-write. I don't like to do that.



    I should have said that right up front.

    ReplyDelete
  3. I assume you mean your collection _property_ :)



    If you're absolutely committed to doing this in a single line with an extension method, you'd need to do something silly like this:



    static T AddToMyCollectionAndReturnMe<T, C>(this T o,

    Converter<T, ICollection<C>> getCollection,

    IEnumerable<C> items) {

    items.ForEach(getCollection(o).Add);

    return o;

    }



    Invoked as:



    var derek = new Person { Name = "Derek", Age = 27 }.AddToMyCollectionAndReturnMe(p => p.Children, GetProgeny());



    That's hilariously bad, IMHO. I would think it nicer to extend to two lines, or just write a utility GetPerson() type method rather than abuse extension methods like that.



    var derek = new Person { Name = "Derek", Age = 27 };

    GetProgeny(derek.Name).ForEach(derek.Children.Add);

    ReplyDelete
  4. Yes, yes, the collection property. :p



    Yeah, that super sucks. I wouldn't even consider doing that.



    The latter approach is similar to what I have, but it's not what I *want*. :)



    It's actually worse than I've shown, because the collection I want to populate is several levels deep in the object initialization expression, so going back and iterating is a PITA.



    I still don't see why this can't be made to work as an extension method. I'm willing to believe that there's something I'm missin, though.

    ReplyDelete
  5. I think it's 3. The C# team seems to have forgotten about the virtue of orthogonality. Extension methods are completely broken IMNSHO. See also http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=90726

    ReplyDelete
  6. This is way out of left field, but... your post reminded me of a tiny little off-the-cuff remark that my Dad made the other day...



    He reminded me that a lot of ancient and primitive (ahem) cultures held (or hold) the belief that if they don't know a person's name, then they don't have any power over them.



    I found that remarkably profound, not least because I'm a programmer and "names" are a big deal for me.



    I guess little TBD's spirit is still free.

    ReplyDelete
  7. How do you add children. Seems very socialist in your object oriented society. Did you get all the paper work filled out for you -0.4 old kid.

    ReplyDelete