Friday, January 27, 2012

System.Collections.IEnumerable and collection initializer syntax

I'd like to make a post about something that I think is one of the least utilized aspects of C#: custom collection initializers. Implementing a custom collection initializer for your class can make your code much more declarative and concise.

It's common to have a collection of things that you set up at design time: drop down list items, configurations, items in a game, a list of mappings, all kinds of things. These are often stored in external xml files or in a sql table. If you're like me then you'd rather have this in code and have it as concise and easy to get right as possible. Here's how.

Look at this example of setting up a list of Armor types for a game:

public List<Armor> ArmorsWithListExample()
{
 List<Armor> armors = new List<Armor>();
 armors.Add(new Armor("leather armor",  1,  250, 10));
 armors.Add(new Armor("chain mail",     4,  500, 14));
 armors.Add(new Armor("splint mail",    7,  750, 16));
 armors.Add(new Armor("plate armor",   10, 1000, 18));
 return armors;
}

Here is the same thing using the collection initializer syntax:

public List<Armor> ArmorsWithInitializedListExample()
{
 return new List<Armor>() {
  new Armor("leather armor",  1,  250, 10),
  new Armor("chain mail",     4,  500, 14),
  new Armor("splint mail",    7,  750, 16),
  new Armor("plate armor",   10, 1000, 18),
 };
}

There are several advantages with the collection initializer syntax. You don't have to have the temporary variable, it's less code with fewer redundancies, and it's a nice declarative way to show that all you are doing is creating the collection. Small advantages, I admit, but they add up. But if you look at some C code, you see that the code can be even more concise by declaring the objects themselves inline. Here's an example from the game Brogue:

const itemTable armorTable[NUMBER_ARMOR_KINDS] = {
 {"leather armor", "", "", 10, 250,  10, {30,30,0},  true, false, "This lightweight armor offers basic protection."},
 {"scale mail",  "", "", 10, 350,  12, {40,40,0},  true, false, "Bronze scales cover the surface of treated leather, offering greater protection than plain leather with minimal additional weight."},
 {"chain mail",  "", "", 10, 500,  13, {50,50,0},  true, false, "Interlocking metal links make for a tough but flexible suit of armor."},
 {"banded mail",  "", "", 10, 800,  15, {70,70,0},  true, false, "Overlapping strips of metal horizontally encircle a chain mail base, offering an additional layer of protection at the cost of greater weight."},
 {"splint mail",  "", "", 10, 1000,  17, {90,90,0},  true, false, "Thick plates of metal are embedded into a chain mail base, providing the wearer with substantial protection."},
 {"plate armor",  "", "", 10, 1300,  19, {120,120,0}, true, false, "Emormous plates of metal are joined together into a suit that provides unmatched protection to any adventurer strong enough to bear its staggering weight."}
};


It's a small improvement but I want to do that in C# as well. With custom collection initializers, you can.

public List<Armor> ArmorsWithCustomInitializerExample()
{
 return new ArmorCollection() {
  { "leather armor",  1,  250, 10 },
  { "chain mail",     4,  500, 14 },
  { "splint mail",    7,  750, 16 },
  { "plate armor",   10, 1000, 18 },
 }.List;
}

Look at that! Concise and easy to get right. Here's the code for the ArmorCollection; notice that it's just a wrapper around a List. The only additional functionality it has is the Add method.

class ArmorCollection : IEnumerable
{
 public List<Armor> List { get; set; }
 
 public ArmorCollection()
 {
  List = new List<Armor>();
 }
  
 IEnumerator IEnumerable.GetEnumerator(){
  return List.GetEnumerator();
 }
  
 public void Add(string name, int weight, int cost, int ac)
 {
  List.Add(new Armor(name, weight, cost, ac));
 }
}

The Add method is what makes it work. According to section 7.6.10.3 of the C# specs, if a class implements the IEnumerable interface then the compiler converts the initializer syntax into a series of calls to the Add method, even though the Add method isn't part of the IEnumerable interface. The Add method can have any number of parameters and the compiler will make it work.

So when you have a collection of things that isn't going to change, instead of using a built-in collection with a series of Add calls, loading from an external xml file, or loading a sql table, consider creating an in memory collection by using a custom initializer: it may be easier, faster, and more concise than the alternatives.

11 comments:

  1. Great read as always! On a side note, are you planning on doing more C# room generation posts? Guess i have grown too addicted to your blog :)

    ReplyDelete
  2. @Anon, thanks! My metroidvania is on hold but March has the 7DRL challenge and I've been thinking about roguelikes again. I'll probably have some posts about that soon. Is there anything specific you're interested in?

    ReplyDelete
  3. Your metroidvania version where you implemented a "loot table" that distributed pickups through the map was of particular interest. It seemed really well implemented regarding density distribution. But hey, I have learned so much from your approaches on programming and design that I'm sure whatever comes next will be a great read! Keep up the awesome work and best of luck for the upcoming 7DRL, will be sure to play it!

    -Tiago

    ReplyDelete
  4. I realise this post is a year old, but a tip better comes late than never. :-)
    Your ArmerCollection can be made simpler:
    (the preview doesn't show angle brackets so I'll replace them with |)

    public class ArmorCollection : List|Armor|
    {
    public void Add(string name, int weight, int cost, int ac)
    {
    Add(new Armor(name, weight, cost, ac));
    }
    }

    ReplyDelete
    Replies
    1. Good point Tom! If it extends List then there's no need to have a List member. That also means it can be used anywhere a List is used.

      Delete
  5. This is good certification program and should teach most or all of these topics, and it's common for schools to give an orientation before teaching starts. The most effective piece of advice.Thanks

    ReplyDelete
  6. It is a good it outsourcing ukraine company which has team of developers, designers, QA and what ellse you need. So instead of hiring your own team on high wages for small project you must outsource.

    ReplyDelete
  7. This is the best published post of your website, all the programming code written here is described in understandable way, so we can get all the stuff easily. This will be helpful for all developers from different it outsource companies, to learn programming skills.

    ReplyDelete
  8. We usually use these syntax in programming as it contains numeric data mostly. This is the requirement of time to use developer programs and these major projects. These initializer are good for you.

    ReplyDelete
  9. This is actually the greatest released publish of the web site, all of the encoding signal created here's referred to within easy to understand method, therefore we are able to obtain everything very easily. This is ideal for just about all designers through various this https://www.besttranslationservices.com/the-most-accurate-english-to-spanish-online-translator/ businesses, to understand encoding abilities.

    ReplyDelete
  10. I liked your post.I stay quite busy with my family but whenever get free i like to visit your this so good site.Its great really.Thanks.

    ReplyDelete