如何在 C# 中构建结构列表数组(具有预定义的数组大小)

Posted

技术标签:

【中文标题】如何在 C# 中构建结构列表数组(具有预定义的数组大小)【英文标题】:How to build Array of Lists of Structs in C# (with predefined size of the Array) 【发布时间】:2020-01-23 21:36:45 【问题描述】:

尝试在 C# 中构建结构列表数组。并通过最佳尝试获得 System.NullReferenceExceptiontest[i].Add(info1); 行错误)

问题根本不是如何避免System.NullReferenceException,而更像是如何快速构建具有预定义数组大小的列表数组,从而能够使用@ 987654323@ 在里面。如果可能不循环整个数组,只需创建列表。

所以这些是要求:

数组的大小应该是预定义的; 每个节点的列表数量应该是任意的,并且应该有可能很容易添加这些列表; 结构应包含struct Info

这是我到目前为止管理的代码(复制和粘贴应该可以复制错误):

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;
    

【问题讨论】:

您只初始化了整个数组,而不是其中的列表。 What is a NullReferenceException, and how do I fix it?的可能重复 @BrootsWaymb - 有没有比在循环前添加 for (int i = 0; i &lt; test.Length; i++) test[i] = new List&lt;Info&gt;(); 更快的方法? test[i] = new List info1; @Vityata,请查看我的回答中的编辑。每个 3 的倍数有 1 个列表,每个 5 的倍数有 1 个列表,每个 3 和 5 的倍数有 2 个列表。 【参考方案1】:

数组的每个元素都没有定义为对象是List

你应该这样做:

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;
    

【讨论】:

或者在第一个循环中完全跳过 if 检查。永远都是真的。 @CodeStranger 是的,把它放在那里,以防他移动东西。如果是我,我会选择 List&lt;Info&gt;[] test = Enumerable.Range(0,entry ).Select(o=&gt;new List&lt;Info&gt;()).ToArray(); 并收工 @Franck - Idk,1 班轮似乎有很多“糖”。一般来说,我想这就像一个标准的性能循环。我正在与那些使用 C++ 和“花哨”向量的人“竞争”,这些人在速度方面具有一般魔力。 (不过 1 班轮很好) @Vityata 它所做的只是初始化数组的所有元素。你不会有一个空值。如果您要迭代所有元素并最终初始化 100% 的数组,那么这一行会更快。编译器似乎喜欢连续多次询问相同的操作,而不是在需要时。同样,最好初始化数组中的所有项目。【参考方案2】:

试试这个:

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;
    

【讨论】:

这会将循环 2 中的新列表分配给循环 1 中已经有列表的位置。第一次命中索引时,这是一种不错的单行方法,否则您将丢失信息(在位置15 例如,就像@Vityata 提到的那样)。 在每次之前添加检查有点过分“补丁”,我可能会使用 1-liner for (int i = 0; i &lt; test.Length; i++) test[i] = new List&lt;Info&gt;(); @Vityata 你可以这样做,但是添加一个空检查比为所有列表分配内存要便宜得多(对于不能被 3 和 5 整除的数字)。但是,如果您打算稍后使用这些列表,那么这似乎是更明智的方法。 @ui_guy - null 检查很快,是的,但是忘记一个的“代价”似乎相当高。我希望有一些“动态”、“快速”和“干净”的东西。但我在这里妥协的一件事是“快速”。【参考方案3】:

试试这个:

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;
    

【讨论】:

感谢您的输入,在一般情况下它更快(只要它跳过无意义的循环)。两个循环的想法是表明,数据必须能够独立输入,因此代码的一部分应该保持原样(例如,2个单独的循环)。 在这种情况下,我只需用var test = Enumerable.Range(0,entry).Select(i=&gt;new List&lt;Info&gt;()).ToArray();初始化所有元素 到目前为止,这似乎是一个不错的选择。 @Franck 在他/她的评论中提出了同样的建议。 我上面发布的代码的好处是可以很容易地转化为懒惰。只需删除.ToArray(),并将其引用为IEnumerable&lt;List&lt;Info&gt;&gt;,它会在您使用它时进行计算。如果你不消耗整个东西,那么你就不需要计算整个数组。 好吧,如果性能绝对是重中之重,那么我建议以老式的方式创建它... var test = List&lt;Info&gt;[entry]; for(var i=0;i&lt;entry;i++) test[i]=new List&lt;Info&gt;(); 代替。不过,性能差异应该很小。

以上是关于如何在 C# 中构建结构列表数组(具有预定义的数组大小)的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 C# 在 Accord.NET 中构建观察数组

C# 中具有固定大小数组的连续分层结构内存?

如何从列表数组(linq 列表)C# 中的所有元素调用方法

如何在 C# 中删除(或取消)泛型数组列表中的对象? [复制]

数据结构与算法(C#)入门 --- 串和数组

如何使用 Newtonsoft.Json 将包含数组数组的 json 对象解析为 C# 中的对象列表?