如何预分配 T 列表的 C# 列表

How to pre-allocate C# List of List of T

我想预先分配一个List<List<double>>。我知道我可以像这样预先分配一维列表:

List<double> MyList = new List<double>(SomeSize);

是否可以对嵌套列表执行此操作?在 C++ 中,我会这样做:

vector<vector<double>> MyList(OuterSize, vector<double>(InnerSize));

我似乎找不到在 C# 中执行此操作的有效方法,但我确信有办法...

没有这种方法,因为它不是一段连续的内存。您必须自己编写程序 - 遍历外部维度。

List 不能真正与 C++ 数组相提并论。你给它分配内存,也就是说你避免了List的增长,但是List以Count=0开始,你必须添加元素,你不能通过索引器访问它。 容量的初始化是可选的!如果您提前知道最终长度,这只是稍微提高了性能。

如果你习惯于定长数组,你可以分配一个多维数组 喜欢

    int[,] x = new int[4,5];

一次。

预分配列表的唯一方法是实例化它。您当然可以实例化外部列表,但是如果您想预分配内部列表,您也必须实例化它们,这意味着您必须预填充外部列表。

你可以使用

var list = Enumerable.Range(1, OuterSize)
     .Select( x => new List<T>(InnerSize) )
     .ToList();

这将创建一个包含 Outersize 个元素的列表,每个元素包含一个空列表,该列表已预先分配为包含 InnerSize 个元素。

但是,使用这种方法您仍然需要将项目添加到内部列表中。如果您想立即使用 ElementAt,那是行不通的。您必须预先填充内部列表。您可以使用:

var list = Enumerable.Range(1, OuterSize)
    .Select
    (
        x => Enumerable.Range(1, InnerSize)
                 .Select( y => default(T) )
                 .ToList()
    )
    .ToList();

这实例化了一个 OuterSize 元素的列表,每个元素都是一个 InnerSize 元素的列表(所有元素都具有相同的 null/default 值)。

您的 C++ 代码使用 vector 的 "fill constructor",它使用表示第二维的列表实例化集合的第一维的所有元素。

C# List<T> 对象没有允许指定初始容量的填充构造函数。现有的填充构造函数保证列表将具有 "sufficient" 由 IEnumerable<T> 参数提供的项目的容量,但它不保证容量将紧密匹配参数可枚举的基数(部分原因是可枚举, 按照设计,不要暴露它们的基数,因此完全匹配容量的唯一方法是一次调整列表的基础数组的一个元素。

您可以用一点 Linq 分两行,用传统循环分三行,构造一个所需容量的空列表,然后添加第二维的对象,每个对象都初始化为所需的容量:

var myList = new List<List<T>>(5);
//Option A: Linq
myList.AddRange(Enumerable.Repeat(0, 5).Select(x => new List<string>(4)));
//Option B: Loop
for(i=0;i<5;i++)
    myList.Add(new List<string>(4));

在某种程度上,您的 C++ 会执行类似于其中任何一个的操作,只是在 C# 中没有任何东西可以在构造函数后面抽象它。


分解第一个选项,List<T>.AddRange() 方法将 IEnumerable<T> 的每个元素添加到列表中,并根据需要调整大小(这里不必这样做,因为我们要加起来指定容量)。所以我们需要生成一个可枚举的列表序列。好吧,有一个 Linq 方法。 Enumerable.Repeat() 方法生成一个可枚举序列,该序列将指定值重复指定次数。但是,如果我们将 "new List()" 指定为要重复的值,该构造函数将只执行一次(在调用 Repeat() 以评估要传递的参数值之前),并且我们将获得对 a 的相同引用单个 List 重复四次。相反,我们想要实例化四个列表,因此我们需要将该构造函数定义为一行可重复的代码,这意味着我们需要一个可以作为方法调用执行的委托。

Enumerable.Repeat() 的重载不接受 Func<T> 和 returns IEnumerable<T>;如果你想用这个方法重复一个函数,你会得到一个 IEnumerable<Func<T>>。但是,可以在可枚举的输入序列上调用 Select<T>() 函数,并在给定可枚举输入的每个元素作为参数的情况下,生成 lambda 语句(匿名委托方法定义的一种形式)所有结果的可枚举序列(我们忽略它是因为它是垃圾;我们只关心输入可枚举的是它有 5 个元素)。所以现在我们有一个 Enumerable<List<string>>,父 List 将进入其内部集合。

是的,我本可以更聪明地做类似的事情:

myList.AddRange(Enumerable.Repeat(()=>new List<string>(4), 5).Select(x => x()));

这做同样的事情,只是 Repeat() 方法重复的不是垃圾数据,它是对执行对象构造的 lambda 的引用,然后在 Select() 方法的拉姆达。但是,我现在有两个 lambda,它们被实现为包含此代码的 class 的私有公式命名函数,这给结果对象增加了比我们真正需要的更多的东西。编译器也无法从 lambda 语句中推断出 Repeat() 函数的通用输出类型,因此上述代码无法编译;我必须明确指定泛型类型,而这行代码(和 C# 作为一种语言)已经足够冗长了。