0

Trying to build Array of Lists of Structs in C#. And getting a System.NullReferenceException with the best try (error on line test[i].Add(info1);)

The question is not how to avoid System.NullReferenceException at all, but more like how to quickly build Array of Lists with predefined size of array, thus that one is able to use array[i].Add(Struct) in it. If possible without looping all over the array, just to create the lists.

So these are the requirements:

  • the size of the array should be predefined;
  • the numbers of the lists per node should be arbitrary, and there should be a possibility that these are added easily;
  • the structure should contain the struct Info.

This is the code, I have managed so far (copy and paste should work, to replicate the error):

using System.Collections.Generic;

class Startup
{
    static void Main()
    {
        int entry = 1233;
        List<Info>[] test = new List<Info>[entry];

        for (int i = 0; i < 500 ; i+=3)
        {
            Info info1 = new Info()
            {
                capacity = i * 2,
                name = i.ToString()
            };
            test[i].Add(info1);
        }
        for (int i = 0; i < 1000; i+=5)
        {
            Info info2 = new Info();
            info2.capacity = i * 2;
            info2.name = i.ToString() + i.ToString();
            test[i].Add(info2);
        }
    }

    struct Info
    {
        public int capacity;
        public string name;
    }
}
Vityata
  • 39,812
  • 7
  • 40
  • 77
  • 2
    You've only initialized the whole array not the Lists within. – Broots Waymb Sep 23 '19 at 19:19
  • 1
    Possible duplicate of [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Code Stranger Sep 23 '19 at 19:23
  • @BrootsWaymb - is there some faster way than adding `for (int i = 0; i < test.Length; i++) { test[i] = new List();}` before the loops? – Vityata Sep 23 '19 at 19:24
  • 2
    test[i] = new List {info1}; – ui_guy Sep 23 '19 at 19:28
  • @Vityata, please take a look at the edits in my answer. Every multiple of 3 has 1 list, every multiple of 5 has 1 list, and every multiple of 3 and 5 has 2 lists. – ui_guy Sep 23 '19 at 19:48

3 Answers3

1

Each element of a the array are not defined as the object is a List

This is how you should do it :

using System.Collections.Generic;
class Startup
{
    static void Main()
    {
        int entry = 1233;
        List<Info>[] test = new List<Info>[entry];

        for (int i = 0; i < 500 ; i+=3)
        {
            Info info1 = new Info()
            {
                capacity = i * 2,
                name = i.ToString()
            };

            // if null initialise the list
            if(test[i] == null) test[i] = new List<Info>();

            test[i].Add(info1);
        }
        for (int i = 0; i < 1000; i+=5)
        {
            Info info2 = new Info();
            info2.capacity = i * 2;
            info2.name = i.ToString() + i.ToString();

            // if null initialise the list
            if(test[i] == null) test[i] = new List<Info>(); 

            test[i].Add(info2);
        }
    }

    struct Info
    {
        public int capacity;
        public string name;
    }
}
Vityata
  • 39,812
  • 7
  • 40
  • 77
Franck
  • 4,167
  • 1
  • 24
  • 48
  • 1
    Or just skip the if check entirely in the firstly loop. It's always going to be true. – Code Stranger Sep 23 '19 at 19:24
  • 2
    @CodeStranger Yes, kept it there simply in case he moves things around. If it was me i would have simply went with `List[] test = Enumerable.Range(0,entry ).Select(o=>new List()).ToArray();` and call it a day – Franck Sep 23 '19 at 19:49
  • @Franck - Idk, that 1 liner seems to have a lot of "sugar". In general I guess it is like a standard loop in performance. And I am "competing" with guys, who use C++ and "fancy" vectors, that do general magic when it comes to speed. (But the 1 liner is good, though) – Vityata Sep 23 '19 at 19:54
  • 1
    @Vityata the one liner all it does is initialize all element of the array. You will not have a single null. If you would be to iterate on all elements and eventually have 100% of the array initialized then that one line will be faster. Compiler seems to like when the same operation is ask many times in a row as opposed to when needed. Again this is better is you initialise all the items in the array. – Franck Sep 23 '19 at 19:57
1

Try this:

using System.Collections.Generic;

class Startup
{
    static void Main()
    {
        int entry = 1233;
        List<Info>[] test = new List<Info>[entry];

        for (int i = 0; i < 500 ; i+=3)
        {
            Info info1 = new Info()
            {
                capacity = i * 2,
                name = i.ToString()
            };
            test[i] = new List<Info> {info1};
        }
        for (int i = 0; i < 1000; i += 5)
        {
            Info info2 = new Info();
            info2.capacity = i * 2;
            info2.name = i.ToString() + i.ToString();
            if (test[i] == null)
            {
                test[i] = new List<Info> { info2 };
            }
            else
            {
                test[i].Add(info2);
            }
        }
    }

    struct Info
    {
        public int capacity;
        public string name;
    }
}
ui_guy
  • 123
  • 8
  • 1
    This assigns a new List in loop 2 to positions that already had a List from loop 1. It's a decent one line approach the first time an index is hit, otherwise you're losing info (in position 15 for example, like @Vityata mentions). – Broots Waymb Sep 23 '19 at 19:33
  • Adding a check before every time is a bit too much of a "patch", I would probably live with the 1-liner `for (int i = 0; i < test.Length; i++) { test[i] = new List();}` – Vityata Sep 23 '19 at 19:49
  • @Vityata You could do that, but adding a null check is far less expensive than allocating the memory for all of the lists (for numbers not divisible by 3 and 5). However, if you were planning to use those lists later anyhow, then it seems the more sensible approach. – ui_guy Sep 23 '19 at 19:54
  • @ui_guy - null check is quick, yes, but the "price" of forgetting one seems to be quite high. I was hoping to have something "dynamic", "quick" and "clean". But the one thing I compromise here is the "quick" one. – Vityata Sep 23 '19 at 20:07
1

Try this:

using System.Collections.Generic;

class Startup
{
  static void Main()
  {
    int entry = 1233;
    var test = Enumerable.Range(0,entry)
      .Select(i=> {
        var y = new List<Info>();
        if(i%3==0 && i < 500)
        {
          y.Add(new Info {
            capacity = i*2,
            name = i.ToString()
          });
        }
        if(i%5==0 && i < 1000)
        {
          y.Add(new Info {
            capacity = i*2,
            name = i.ToString() + i.ToString()
          });
        }
        return y;
      }).ToArray();
    }

    struct Info
    {
        public int capacity;
        public string name;
    }
}
Robert McKee
  • 20,124
  • 1
  • 35
  • 53
  • Thanks for the input, in the general case it is faster (as far as it skips the meaningless loops). The idea of the two loops was to show, that data has to be able to be entered independently, thus that part of the code should stay as it is (e.g., 2 separate loops). – Vityata Sep 23 '19 at 20:01
  • 2
    In that case, then I would just initialize all the elements with `var test = Enumerable.Range(0,entry).Select(i=>new List()).ToArray();` – Robert McKee Sep 23 '19 at 20:05
  • This seems to be quite a good option so far. @Franck proposed the same in his/her comment. – Vityata Sep 23 '19 at 20:09
  • 1
    The nice thing about the code I posted above is that can be easily transformed into being lazy. Just remove the `.ToArray()`, and reference it as being an `IEnumerable>` instead, and it get calculcated as your consume it. If you don't consume the whole thing, then you don't need to calculcate the entire array. – Robert McKee Sep 23 '19 at 20:09
  • Well if performance is absolutely top priority, then I would suggest creating it the old fashioned way... `var test = List[entry]; for(var i=0;i();` instead. The performance difference should be pretty minimal though. – Robert McKee Sep 23 '19 at 20:12
  • Well, I am a bit afk now, but the LINQ is in slow in general, thus the difference should be visible, when 10^6 entries are given as an input or so. Will see tomorrow. – Vityata Sep 23 '19 at 20:18
  • On my machine, the LINQ version runs in about .0000522 seconds. The optimized one I gave above runs in about .0000198 seconds. I rarely worry about that kind of difference, but it can matter if it's in a tight loop. If you are initializing much larger numbers, then start to considering threading even... `var test = new List[entry]; Parallel.For(0, entry,i=>{ test[i]=new List(); });` start to look good around 8000 entries I believe.. depending on hardware, and there are much better ways than that. – Robert McKee Sep 23 '19 at 20:38