Using collection initializer syntax on custom types?
Asked Answered
C

5

11

I have a large static list which is basically a lookup table, so I initialise the table in code.

private class MyClass
{
    private class LookupItem
    {
        public int Param1    { get; set; }
        public int Param2    { get; set; }
        public float Param2  { get; set; }
        public float Param4  { get; set; }
    }

    private static List<LookupItem> _lookupTable = new List<LookupItem>()
    { 
        new LookupItem() { Param1 = 1, Param2 = 2, Param3 = 3 Param4 = 4 },
        new LookupItem() { Param1 = 5, Param2 = 6, Param3 = 7 Param4 = 8 },
        //etc
    }
}

The real LookupItem has many more properties, so I added a constructor to allow for a more compact initialisation format:

private class MyClass
{
    private class LookupItem
    {
        public int Param1    { get; set; }
        public int Param2    { get; set; }
        public float Param2  { get; set; }
        public float Param4  { get; set; }

        public LookupItem(int param1, int param2, float param3, float param4)
        {
            Param1 = param1;
            Param2 = param2;
            Param3 = param3;
            Param4 = param4;    
        }
    }

    private static List<LookupItem> _lookupTable = new List<LookupItem>()
    { 
        new LookupItem(1, 2, 3, 4),
        new LookupItem(5, 6, 7, 8),
        //etc
    }
}

What I'd really like to do is use the collection initialiser format for the object itself so I can get rid of the new LookupItem() on every line. eg:

private static List<LookupItem> _lookupTable = new List<LookupItem>()
{ 
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 },
    //etc
}

Is this possible? I like to think it is because the KeyValuePair's of a Dictionary<> can be initialised in this way.

MSDN States:

Collection initializers let you specify one or more element intializers when you initialize a collection class that implements IEnumerable. The element initializers can be a simple value, an expression or an object initializer. By using a collection initializer you do not have to specify multiple calls to the Add method of the class in your source code; the compiler adds the calls.

Does this mean I need to implement IEnumerable on my LookupItem class and return each parameter? My class isn't a collection class though.

Cooperation answered 8/2, 2012 at 13:36 Comment(0)
W
16

I think you need to make a custom collection instead of List. Call it LookupItemTable, for example. Give that collection an Add(int, int, float, float) method and have it implement IEnumerable. For example:

class LookupItem
{
    public int a;
    public int b;
    public float c;
    public float d;
}

class LookupItemTable : List<LookupItem>
{
    public void Add(int a, int b, float c, float d)
    {
        LookupItem item = new LookupItem();
        item.a = a;
        item.b = b;
        item.c = c;
        item.d = d;
        Add(item);
    }
}

private static LookupItemTable _lookupTable = new LookupItemTable {
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 }
};

I've now tried the above code and it seems to work for me.

Willywilly answered 8/2, 2012 at 13:42 Comment(0)
M
6

Quick fix : Make your own List type with an Add overload that takes multiple arguments:

class LookupList : List<LookupItem> {
    public void Add(int Param1, int Param2, ... sometype ParamX) {
        this.Add(new LookupItem() { Param1 = Param1, Param2 = Param2, ... ParamX = ParamX });
    }
}

Now works exactly as you want:

    private static LookupList _lookupTable = new LookupList() {                  
        {1,2,3,4},                 
        {2,7,6,3}                
    };

More fundamental answer:

You're mixing up object initializers and collection initializers. Put simply:

Object initializers are a syntactic trick that, in the background call the property set methods for each named property with the value specified.

Collection initializers are a syntactic trick that, in the background:

  • For an Array type: Fill the array with the items
  • For any other type, which must implement IEnumerable: Call the Add method for each sub-bracketed set.

That is all there is to it. Consider for example the following hack:

public class Hack : IEnumerable {
    public int LuckyNumber { get; set; }
    public double Total { get; private set; }
    public void Add(string message, int operand1, double operand2, double operand3) {
        Console.WriteLine(message);
        this.Total += operand1 * operand2 - operand3;
    }
    public IEnumerator GetEnumerator() { throw new NotImplementedException(); }
}

class Program {
    static void Main(string[] args) {
        Hack h1 = new Hack() {
            { "Hello", 1, 3, 2},
            { "World", 2, 7, 2.9}
        };
        Console.WriteLine(h1.Total);
        Hack h2 = new Hack() { LuckyNumber = 42 };
        Console.WriteLine(h2.LuckyNumber);
    }
}

You should never do this in a real program, but I hope examining this example and the results, especially if you debug step through it, will help you understand the initializers clearly and choose a good solution for your current scenario.

Mauricemauricio answered 8/2, 2012 at 14:23 Comment(1)
You should never do this in a real program - okay. What should you do in a real program? Scenario: porting from Java, and have a class that has 3 Add() overloads and implements IEnumerable<T>. If I implement ICollection<T> I will have a bunch of Remove(), Clear() methods that I don't want or need. Is there another solution to make it more clear how to use the collection initializers using Intellisense, or is writing documentation my only option?Cognoscenti
L
4

You're trying to use a collection initializer on the list itself, not on your type:

// Note the "new List<...>" part - that specifies what type the collection
// initializer looks at...
private static List<LookupItem> _lookupTable = new List<LookupItem>()
{ 
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 },
}

So it's looking for an Add method with four parameters on List<T>, and that doesn't exist.

You would have to implement your own collection class to use a custom collection initializer. While you're using List<T>, you're stuck with the constructor calls you've got.

Leveille answered 8/2, 2012 at 13:42 Comment(0)
S
1

Rather than obscure the intent of List with a custom class deriving from List<LookupItem>, add a simple extension that calls the required constructor:

public static class LookupItemListExtensions
{
    public static void Add(this List<LookupItem> lookupItemList, int param1, int param2, float param3, float param4)
    {
        lookupItemList.Add(new LookupItem(param1, param2, param3, param4));
    }
}

Note that you're trading clarity for brevity, so use at your own risk. Using "new ListItem" lets you F12 directly to the constructor; this extension does not (nor will it ever likely be obvious to other developers).

Shaggy answered 2/1, 2019 at 20:6 Comment(0)
S
0

Does this mean I need to implement IEnumerable on my LookupItem class and return each parameter? My class isn't a collection class though.

No, it means that List<LookupItem> implements IEnumerable, which is why you can write

private static List<LookupItem> _lookupTable = new List<LookupItem>()
{ 
    new LookupItem(1, 2, 3, 4),
    new LookupItem(5, 6, 7, 8),
    //etc
}

It also means that if your LookupItem was a collection that implemented IEnumerable, you could have written:

private static List<LookupItem> _lookupTable = new List<LookupItem>()
{ 
    new LookupItem { new Item(), new Item() },
    new LookupItem { new Item(), new Item() }
}
Susette answered 8/2, 2012 at 13:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.