如何将List <T>初始化为给定的大小(而不是容量)?


112

.NET提供了一个性能几乎相同的通用列表容器(请参阅阵列性能与列表性能问题)。但是,它们在初始化方面有很大的不同。

数组很容易使用默认值初始化,并且根据定义,它们已经具有一定的大小:

string[] Ar = new string[10];

这样可以安全地分配随机项目,例如:

Ar[5]="hello";

与列表相比,事情更加棘手。我可以看到执行相同初始化的两种方法,这两种都不是您所说的优雅:

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);

要么

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);

什么是更清洁的方式?

编辑:到目前为止的答案涉及容量,这是除预填充列表以外的其他功能。例如,在刚创建的容量为10的列表上,一个不能执行L[2]="somevalue"

编辑2:人们想知道为什么我要以这种方式使用列表,因为这不是他们打算使用的方式。我可以看到两个原因:

  1. 令人信服的是,列表是“下一代”数组,几乎没有损失,却增加了灵活性。因此,默认情况下应使用它们。我指出,它们可能不那么容易初始化。

  2. 我目前正在编写的是一个基类,该基类在更大的框架中提供了默认功能。在我提供的默认功能中,列表的大小是高级已知的,因此我可以使用数组。但是,我想为任何基类提供动态扩展它的机会,因此我选择了一个列表。


1
“编辑:到目前为止,答案是容量,这是预填充列表以外的其他功能。例如,在刚刚创建的容量为10的列表上,不能执行L [2] =“ somevalue””修改,也许您应该改写问题标题...
aranasaurus

但是,用空值预填充列表的用途是什么,导致topicstarter试图这样做?
Frederik Gheysels,2009年

1
如果位置映射至关重要,那么使用Dictionary <int,string>会更有意义吗?
格雷格D

7
List不能代替Array。他们解决截然不同的问题。如果您想要固定大小,则需要一个Array。如果使用List,则表示做错了。
Zenexer 2013年

5
我总是找到答案,试图加重诸如“我看不到为什么会需要……”之类的争论。这仅意味着:您看不到它。它并不一定意味着其他。我尊重人们想提出解决问题的更好方法,但是应该用谦逊的措辞来表达,例如:“您确定需要清单吗?也许您还告诉我们更多有关您的问题...”。这样,它变得令人愉悦,参与,鼓励OP改善他们的问题。成为赢家-谦虚。
AnorZaken

Answers:


79

我不能说我经常需要-您能否提供更多为什么要这样做的详细信息?我可能会将其作为静态方法放在帮助器类中:

public static class Lists
{
    public static List<T> RepeatedDefault<T>(int count)
    {
        return Repeated(default(T), count);
    }

    public static List<T> Repeated<T>(T value, int count)
    {
        List<T> ret = new List<T>(count);
        ret.AddRange(Enumerable.Repeat(value, count));
        return ret;
    }
}

可以使用,Enumerable.Repeat(default(T), count).ToList()但是由于缓冲区大小调整,效率会很低。

注意,如果T是引用类型,它将存储countvalue参数传递的引用的副本-因此它们都将引用同一对象。这可能取决于您的用例,也可能不是您想要的。

编辑:如注释中所述,如果需要,您可以Repeated使用循环来填充列表。那也会稍微快一点。我个人认为代码使用的是Repeat更具描述性的,并且怀疑在现实世界中,性能差异是无关紧要的,但是您的工作量可能会有所不同。


1
我意识到这是一个旧帖子,但我很好奇。根据链接的最后一部分(dotnetperls.com/initialize-array),与for循环相比,Enumerable.Repeat的性能要差得多。此外,根据msdn,AddRange()具有O(n)复杂度。使用给定的解决方案而不是简单的循环是否有点适得其反?
2014年

@Jimmy:两种方法都是O(n),我发现这种方法更能说明我要实现的目标。如果您喜欢循环,请随时使用。
乔恩·斯基特

@Jimmy:还请注意那里的基准是using Enumerable.Repeat(...).ToArray(),这不是我的使用方式。
乔恩·斯基特

1
Enumerable.Repeat()以以下方式使用了它(Pair与C ++等效,实现方式一样):Enumerable.Repeat( new Pair<int,int>(int.MaxValue,-1), costs.Count)注意到副作用,即已List充满到单个对象的引用副本。改变元素就像myList[i].First = 1改变了整个元素List。我花了几个小时才找到这个错误。你们知道这个问题有什么解决方法.Add(new Pair...)吗(除了使用通用循环并使用?)
00zetti

1
@ Pac0,我在下面添加了一个答案。修改内容可能会被拒登。
杰里米·雷·布朗

136
List<string> L = new List<string> ( new string[10] );

3
我个人认为这是最彻底的方法-虽然它在体内的问题提到潜在的笨拙-不知道为什么
hello_earth

4
+1:遇到一种情况,在通过索引填充之前,我需要使用固定的一组空值初始化可变大小列表(然后再添加索引,因此不适合使用数组)。我的答案是实用且简单。
Gone Coding

神话般的答案。@GoneCoding我只是想知道在内部新初始化的列表是否L会简单地按string[10]原样重用内存(列表的后备存储也是数组)还是会分配自己的新内存,然后复制的内容string[10]string[10]如果L选择后面的路线,将自动收集垃圾。
RBT

3
@RBT这是此方法的唯一缺点:数组不会被重用,因此您将立即拥有该数组的两个副本(如您所知,List在内部使用数组)。在极少数情况下,如果您有大量的元素或内存限制,可能会出现问题。是的,一旦List构造函数完成,多余的数组将有资格进行垃圾回收(否则会造成可怕的内存泄漏)。请注意,符合条件并不意味着“立即收集”,而是下次运行垃圾收集时。
AnorZaken

2
此答案分配10个新字符串-然后遍历它们,并根据需要复制它们。如果您正在处理大型阵列,那么甚至不要考虑这一点,因为它需要的内存是接受的答案的两倍。
UKMonkey

22

使用将int(“容量”)作为参数的构造函数:

List<string> = new List<string>(10);

编辑:我应该补充一点,我同意弗雷德里克。您使用列表的方式与首先使用列表的全部原因背道而驰。

编辑2:

编辑2:我当前正在编写的是一个基类,该基类提供默认功能,作为更大框架的一部分。在我提供的默认功能中,列表的大小是高级已知的,因此我可以使用数组。但是,我想为任何基类提供动态扩展它的机会,因此我选择了一个列表。

为什么有人需要知道具有所有空值的列表的大小?如果列表中没有实际值,则我希望长度为0。无论如何,这很笨拙,这表明它与类的预期用途背道而驰。


46
这个答案并不在列表中拨出10个空条目(这是要求),它只是10项分配空间列表的大小调整之前,需要(如容量),因此这并没有什么不同,以new List<string>()尽可能的问题出。虽然做得很好,但做得很好:)
Gone Coding

2
重载的构造函数是“初始容量”值,而不是“大小”或“长度”,并且也不会初始化项目
Matt Wilko 2014年

要回答“为什么有人需要它”:现在,我需要这个用于深度克隆树数据结构。一个节点可能会或可能不会填充其子节点,但是我的基本节点类需要能够使用其所有子节点来克隆自身。等等,更复杂。但是我既需要通过填充我仍然为空的列表,list[index] = obj;又需要使用其他一些列表功能。
Bitterblue

3
@Bitterblue:另外,任何类型的预分配映射都可能没有预先包含所有值。当然有用途;我在经验不足的日子写了这个答案。
Ed S.

8

首先创建一个数组,其中包含所需的项目数,然后将其转换为列表。

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();

7

如果要使用固定值初始化列表,为什么要使用列表?我可以理解,出于性能考虑,您想要为其提供初始容量,但这不是列表在常规数组上的优势之一,列表可以在需要时增长吗?

执行此操作时:

List<int> = new List<int>(100);

您创建一个列表,其容量为100个整数。这意味着您的列表在添加第101个项目之前不需要“增长”。列表的基础数组将初始化为100。


1
“如果要使用固定值初始化列表,为什么要使用列表”这是一个好主意。
Ed S.

7
他要求一个列表,其中每个元素都已初始化,并且列表具有大小,而不仅仅是容量。现在的答案是错误的。
Nic Foster

这个问题需要一个答案,而不是对被问到的批评。有时有理由这样做,而不是使用数组。如果对为什么会发生这种情况感兴趣,可以提出一个堆栈溢出问题。
Dino Dini

5

像这样初始化列表的内容并不是真正的列表用途。列表旨在容纳对象。如果要将特定数字映射到特定对象,请考虑使用键值对结构(例如哈希表或字典)而不是列表。


这不能回答问题,而只是给出一个不被问到的理由。
Dino Dini

5

您似乎在强调需要与数据进行位置关联,因此关联数组会更合适吗?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";

这是对所要回答的一个不同的问题。
Dino Dini

4

如果要使用固定值的N个元素初始化列表,请执行以下操作:

public List<T> InitList<T>(int count, T initValue)
{
  return Enumerable.Repeat(initValue, count).ToList();
}

有关使用此技术调整缓冲区大小的关注,请参见John Skeet的答案。
艾米B

1

您可以使用Linq巧妙地使用默认值初始化列表。(类似于David B的回答。)

var defaultStrings = (new int[10]).Select(x => "my value").ToList();

再向前走一步,并使用不同的值“字符串1”,“字符串2”,“字符串3”等初始化每个字符串:

int x = 1;
var numberedStrings = (new int[10]).Select(x => "string " + x++).ToList();

1
string [] temp = new string[] {"1","2","3"};
List<string> temp2 = temp.ToList();

List <string> temp2如何= new List <string>(temp); 就像已经建议的OP。
Ed S.

Ed-这个人确实回答了问题-这与容量无关。
波阿斯

但是,OP已经表示他不喜欢这种解决方案。
Ed S.

1

接受的答案(带有绿色复选标记的答案)存在问题。

问题:

var result = Lists.Repeated(new MyType(), sizeOfList);
// each item in the list references the same MyType() object
// if you edit item 1 in the list, you are also editing item 2 in the list

我建议更改上面的行以执行对象的副本。关于此有许多不同的文章:

如果要使用默认构造函数而不是NULL初始化列表中的每个项目,请添加以下方法:

public static List<T> RepeatedDefaultInstance<T>(int count)
    {
        List<T> ret = new List<T>(count);
        for (var i = 0; i < count; i++)
        {
            ret.Add((T)Activator.CreateInstance(typeof(T)));
        }
        return ret;
    }

1
这不是“问题”,而是复制引用的正常行为。在不知道情况的情况下,您无法知道是否适合复制对象。问题不是关于列表是否应填充副本,而是关于初始化具有容量的列表。我要澄清我的答案在它的行为方面确实有,但我真的不认为他会成为这个问题的背景问题。
乔恩·斯基特

@JonSkeet,我是在回复静态助手,对于原始类型来说可以,但对于引用类型则可以。用相同的引用项目初始化列表的情况似乎不正确。在这种情况下,如果列表中的每个项目都指向堆上的同一对象,为什么还要有列表。
杰里米·雷·布朗

示例场景:您想填充一个字符串列表,首先为每个元素输入一个“未知”条目,然后针对特定元素修改该列表。在这种情况下,所有“未知”值都引用同一字符串是完全合理的。每次克隆字符串都是没有意义的。在一般意义上,用多个引用引用同一对象来填充字符串不是“正确”或“错误”。只要读者知道行为是什么,就由他们决定行为是否符合他们的特定用例
乔恩·斯基特

0

关于IList的通知: MSDN IList备注:“ IList实现分为三类:只读,固定大小和可变大小。(...)。有关此接口的通用版本,请参见 System.Collections.Generic.IList<T>。”

IList<T>不继承自IList(但同时List<T>实现IList<T>IList),但始终为可变大小。从.NET 4.5开始,我们还拥有IReadOnlyList<T>AFAIK,因此没有所需的固定大小的通用列表。


0

这是我用于单元测试的样本。我创建了一个类对象列表。然后,我使用forloop添加了我期望从服务中获得的“ X”个对象。这样,您可以添加/初始化任何给定大小的列表。

public void TestMethod1()
    {
        var expected = new List<DotaViewer.Interface.DotaHero>();
        for (int i = 0; i < 22; i++)//You add empty initialization here
        {
            var temp = new DotaViewer.Interface.DotaHero();
            expected.Add(temp);
        }
        var nw = new DotaHeroCsvService();
        var items = nw.GetHero();

        CollectionAssert.AreEqual(expected,items);


    }

希望我能对你们有所帮助。


-1

有点晚了,但是您提出的第一个解决方案对我来说似乎更干净:您不会两次分配内存。甚至List构造函数也需要遍历数组以进行复制;它甚至不知道事先只有空元素。

1.-分配N-循环N成本:1 *分配(N)+ N *循环迭代

2.-分配N-分配N +循环()成本:2 *分配(N)+ N *循环迭代

但是,由于List是内置类,所以List的循环分配可能更快,但是C#是jit编译的……

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.