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

Boa*_*oaz 99 .net c# generics initialization list

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

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

string[] Ar = new string[10];
Run Code Online (Sandbox Code Playgroud)

这允许人们安全地分配随机项目,比如说:

Ar[5]="hello";
Run Code Online (Sandbox Code Playgroud)

列表事情更棘手.我可以看到两种方法进行相同的初始化,这两种方式都不是你所谓的优雅:

List<string> L = new List<string>(10);
for (int i=0;i<10;i++) L.Add(null);
Run Code Online (Sandbox Code Playgroud)

要么

string[] Ar = new string[10];
List<string> L = new List<string>(Ar);
Run Code Online (Sandbox Code Playgroud)

什么是更干净的方式?

编辑:到目前为止的答案是指容量,这不是预先填充列表.例如,在刚创建的容量为10的列表中,无法做到L[2]="somevalue"

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

  1. 人们可以非常有说服力地认为列表是"下一代"阵列,增加灵活性几乎不会受到惩罚.因此,默认情况下应该使用它们.我指出它们可能不那么容易初始化.

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

小智 115

List<string> L = new List<string> ( new string[10] );
Run Code Online (Sandbox Code Playgroud)

  • +1:刚刚遇到一个情况,我需要一个可变大小的列表,在通过索引填充之前用一组固定的空值进行初始化(然后添加额外的数据,因此数组不合适).这个答案得到了实际和简单的投票. (4认同)
  • 我个人认为这是最干净的方式 - 虽然在问题机构中提到它可能是笨拙的 - 不知道为什么 (3认同)
  • @RBT这是此方法的唯一缺点:*不会*重复使用该数组,因此您将立即拥有该数组的两个副本(如您所知,List在内部使用数组)。在极少数情况下,如果元素数量过多或存在内存限制,则可能会出现问题。是的,一旦List构造函数完成,多余的数组将有资格进行垃圾回收(否则将造成严重的内存泄漏)。请注意,符合条件并不意味着“立即收集”,而是下次运行垃圾收集时。 (2认同)
  • 此答案分配10个新字符串-然后遍历它们,并根据需要将其复制。如果您正在使用大型阵列;那么甚至不要考虑这一点,因为它需要的内存是接受的答案的两倍。 (2认同)

Jon*_*eet 72

我不能说我经常需要这个 - 你能否详细说明你为什么要这样做?我可能会把它作为一个静态方法放在一个帮助器类中:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

可以使用,Enumerable.Repeat(default(T), count).ToList()但由于缓冲区大小调整,这将是低效的.

编辑:如评论中所述,如果您愿意,可以T使用循环填充列表.那也会稍快一些.就个人而言,我发现代码使用count更具描述性,并怀疑在现实世界中性能差异是无关紧要的,但您的里程可能会有所不同.

  • 我意识到这是一篇旧文章,但我很好奇。根据链接的最后部分(http://www.dotnetperls.com/initialize-array),与 for 循环相比,Enumerable.Repeat 的性能要差得多。另外,根据 msdn,AddRange() 的复杂度为 O(n)。使用给定的解决方案而不是简单的循环不是有点适得其反吗? (2认同)
  • @Jimmy:两种方法都是 O(n),我发现这种方法更能描述我想要实现的目标。如果您喜欢循环,请随意使用它。 (2认同)
  • @Jimmy:另请注意,基准测试使用“Enumerable.Repeat(...).ToArray()”,这*不是*我使用它的方式。 (2认同)
  • 我以下列方式使用了 `Enumerable.Repeat()`(实现 `Pair` 就像 C++ 等价物一样): `Enumerable.Repeat( new Pair&lt;int,int&gt;(int.MaxValue,-1), cost .Count)` 注意到副作用,即`List` 充满了对单个对象的引用副本。更改像 `myList[i].First = 1` 这样的元素会更改整个 `List` 中的每个元素。我花了几个小时才找到这个错误。你们知道这个问题的任何解决方案吗(除了只使用一个公共循环并使用`.Add(new Pair...)`? (2认同)

Ed *_* S. 23

使用以int("capacity")作为参数的构造函数:

List<string> = new List<string>(10);
Run Code Online (Sandbox Code Playgroud)

编辑:我应该补充一点,我同意弗雷德里克.您使用List的方式与首先使用它的整个推理背道而驰.

EDIT2:

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

为什么有人需要知道具有所有空值的List的大小?如果列表中没有实际值,我希望长度为0.无论如何,这是cludgy的事实表明它违背了类的预期用途.

  • 这个答案没有在列表中分配10个空条目(这是要求),它只是在需要调整列表大小*(即容量)之前为10个条目*分配空间,所以这与`new List <没什么不同string>()`就问题而言.尽管获得了如此多的票数,但做得很好:) (35认同)
  • @Bitterblue:此外,任何类型的预分配映射,您可能没有预先拥有所有值.肯定有用处; 我在经验不足的日子里写了这个答案. (3认同)
  • 重载的构造函数是“初始容量”值,而不是“大小”或“长度”,并且它也不初始化项目 (2认同)
  • 回答“为什么有人需要这个”:我现在需要这个来进行深度克隆树数据结构。节点可能会也可能不会填充其子节点,但我的基节点类需要能够使用其所有子节点来克隆自身。等等,等等,这更复杂了。**但我需要通过 `list[index] = obj;` 填充我仍然为空的列表,并使用一些其他列表功能。** (2认同)

Cha*_*ace 7

这是一个老问题,但我有两个解决方案。一是快速而肮脏的反射;另一种是实际回答问题的解决方案(设置大小而不是容量),同时仍然具有高性能,但这里的答案都没有做到这一点。


反射

这既快又脏,而且代码的作用应该非常明显。如果您想加快速度,请缓存 GetField 的结果,或创建一个 DynamicMethod 来执行此操作:

public static void SetSize<T>(this List<T> l, int newSize) =>
    l.GetType().GetField("_size", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(l, newSize);
Run Code Online (Sandbox Code Playgroud)

显然很多人会对将这样的代码投入生产犹豫不决。


ICollection<T>

该解决方案基于以下事实:构造函数List(IEnumerable<T> collection)会优化ICollection<T>大小并立即将大小调整到正确的量,而无需进行迭代。然后它调用集合CopyTo来进行复制。

构造函数的代码List<T>如下:

public List(IEnumerable<T> collection) {
....
    ICollection<T> c = collection as ICollection<T>;
    if (collection is ICollection<T> c)
    {
        int count = c.Count;
        if (count == 0)
        {
            _items = s_emptyArray;
        }
        else {
            _items = new T[count];
            c.CopyTo(_items, 0);
            _size = count;
        }
    }    
Run Code Online (Sandbox Code Playgroud)

因此我们可以完全优化地将 List 预初始化为正确的大小,而无需任何额外的复制。

为何如此?通过创建一个ICollection<T>除了返回 a 之外不执行任何操作的对象Count。具体来说,我们不会实现任何CopyTo调用唯一其他函数的东西。

private struct SizeCollection<T> : ICollection<T>
{
    public SizeCollection(int size) =>
        Count = size;

    public void Add(T i){}
    public void Clear(){}
    public bool Contains(T i)=>true;
    public void CopyTo(T[]a, int i){}
    public bool Remove(T i)=>true;
    public int Count {get;}
    public bool IsReadOnly=>true;
    public IEnumerator<T> GetEnumerator()=>null;
    IEnumerator IEnumerable.GetEnumerator()=>null;
}

public List<T> InitializedList<T>(int size) =>
    new List<T>(new SizeCollection<T>(size));
Run Code Online (Sandbox Code Playgroud)

理论上,我们可以对现有数组做同样的事情AddRangeInsertRange这也解释了ICollection<T>,但是那里的代码为假定的项目创建了一个新数组,然后将它们复制进去。在这种情况下,清空会更快 -环形Add

public void SetSize<T>(this List<T> l, int size)
{
    if(size < l.Count)
        l.RemoveRange(size, l.Count - size);
    else
        for(size -= l.Count; size > 0; size--)
            l.Add(default(T));
}
Run Code Online (Sandbox Code Playgroud)


Fre*_*els 6

如果要使用固定值初始化它,为什么使用List?我可以理解 - 为了性能 - 你想给它一个初始容量,但它不是一个列表比常规数组的优点之一,它可以在需要时增长?

当你这样做:

List<int> = new List<int>(100);
Run Code Online (Sandbox Code Playgroud)

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

  • 他要求列出一个列表,其中每个元素都已初始化,列表的大小不仅仅是容量.这个答案现在不正确. (3认同)
  • “如果你想用固定值初始化它,为什么要使用列表” 一个很好的观点。 (2认同)

Amy*_*y B 6

如果你想用 N 个固定值的元素初始化列表:

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


min*_*998 6

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

int[] fakeArray = new int[10];

List<int> list = fakeArray.ToList();
Run Code Online (Sandbox Code Playgroud)


Wel*_*bog 5

初始化列表的内容不是真正的列表.列表旨在容纳对象.如果要将特定数字映射到特定对象,请考虑使用键值对结构,如散列表或字典而不是列表.

  • 这并没有回答问题,而只是给出了不问这个问题的理由。 (6认同)

Gre*_*g D 5

您似乎强调需要与数据进行位置关联,那么关联数组不是更合适吗?

Dictionary<int, string> foo = new Dictionary<int, string>();
foo[2] = "string";
Run Code Online (Sandbox Code Playgroud)

  • 这是回答与所问问题不同的问题。 (2认同)