【问题标题】:Assigning values to a list vs an array and ArgumentOutOfRangeException将值分配给列表与数组和 ArgumentOutOfRangeException
【发布时间】:2016-05-16 20:01:45
【问题描述】:

为什么给List赋值必须用Add,而给数组赋值可以用[]操作符?

例如:

    string[] y = new string[10];
    y[0] = "asdf"; //fine

    List<string> x = new List<string>(10);
    x[0] = "asdf"; //ArgumentOutOfRangeException 

两者不应该有相同的行为吗?

【问题讨论】:

  • Shouldn't both have the same behavior? 没有。为什么应该是一样的?它们不是一回事......
  • 您需要先将字符串“asdf”添加到x,然后才能使用它。 x.add("asdf")。现在列表是空的。
  • 你为什么认为他们会有相同的行为?列表和数组是不同的东西。
  • 我假设 c# List 与 c++ vector 相同。
  • @kuhaku 这是相同的想法,但实现不必相同。

标签: c# arrays collections syntax


【解决方案1】:

查看 List(of T) 的源代码,您会看到索引属性 getter/setter 如下所示:

    // Sets or Gets the element at the given index.
    //
    public T this[int index] {
        get { 
            // Fllowing trick can reduce the range check by one
            if ((uint) index >= (uint)_size) { 
                ThrowHelper.ThrowArgumentOutOfRangeException(); 
            }
            return _items[index]; 
        }
        set {
            if ((uint) index >= (uint)_size) {
                ThrowHelper.ThrowArgumentOutOfRangeException(); 
            }
            _items[index] = value; 
            _version++; 
        }
    } 

请注意,在设置 List 的内部数组中的相应项之前,它首先检查私有 _size 变量以确保它在范围内。但是,_size 未设置为数组的大小。大小在 List 的各种 Add/Remove 方法中递增/递减,因此即使您实例化一个初始容量为 10 的列表,这也是 List 数组的 internal 容量。这是构造函数:

    // Constructs a List with a given initial capacity. The list is
    // initially empty, but will have room for the given number of elements 
    // before any reallocations are required. 
    //
    public List(int capacity) { 
        if (capacity < 0) ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.capacity, ExceptionResource.ArgumentOutOfRange_SmallCapacity);
        _items = new T[capacity];
    }

除非您使用 Add/Remove/AddRange/etc,否则不会设置 _size(因此保持其初始值 0)。或使用接受 IEnumerable 的构造函数(在这种情况下,大小取决于 IEnumerable 中的项目数)。

如果您考虑一下,这是有道理的。列表的想法是,当容量需要更改时,您不必担心数字索引和调整大小/复制数组的复杂性(和丑陋)。在 List 实例化之后,内部数组的大小应该与开发人员无关。如果您想对内部数组的使用方式进行微观管理,那么您应该创建自己的实现,或者只使用数组。

【讨论】:

    【解决方案2】:

    您实现数组的方式是正确的。

    数组的大小需要在创建时声明。没有办法解决这个问题。

    不过,列表的大小更加灵活。您可以根据需要添加任意数量的元素,而无需声明初始大小。但是,添加元素后,您可以通过它们的索引号访问或编辑它们。这是一个例子。

    您遇到了该异常,因为从技术上讲,在您实际向其添加值之前,列表不会填充索引。让我知道这是否可以解决问题。

        //You can add your elements when you instantiate it
        List<string> names = new List<string>{"Alex", "Tommy", "Bob"};
    
        //Or you can add them later
        List<string> cities = new List<string>();
    
        cities.Add("Denver");
        cities.Add("New York");
    
        //Now that they are created you can access or edit any of the elements within them.
        names[2] = "Gerard";
        cities[1] = "San Francisco";
    

    【讨论】:

      【解决方案3】:

      列表的内部结构与array 不同。虽然在array 中,您在其定义中定义了一定大小的项目,但拥有这些对象所需的内存量由 CLR 在内存中重新分配。

      在list&lt;T&gt; 中,您可以定义列表中的最大项目数。这就是(部分)您必须调用Add 方法来在list&lt;T&gt; 中添加对象的原因。您可以像在构造函数中所做的那样为列表定义初始 Capacity。如果您需要添加的容量超过容量,列表将为您重新排列。该框架会为您管理列表中有多少项目。

      另一个重要的事情是,在这两种情况下,您都可以通过index 访问。示例:

      var obj = list[1];
      var obj2 = array[1];
      

      如果您在list&lt;T&gt;/array 上没有1 索引,则在array 中,您将获得default(T)(考虑T 作为您的类型)并在列出您将获得Exception。

      【讨论】:

      • 我喜欢思考它的方式是,当你初始化一个数组时,你有一组框。当您使用大小参数初始化列表时,您有一个可以容纳一组盒子的仓库(但在您 Add() 它们之前没有盒子)
      • 谢谢@Steve,你是对的。我已经更新了我的答案。
      • “列表的内部结构与数组不同”和“但不会在内存中重新分配”的说法并不准确。一个列表,内部是一个数组。 List 类只是一个处理调整大小、复制等的包装器。
      【解决方案4】:

      此构造函数不会在列表中创建任何元素。它只是为可以添加到此列表中的项目保留内存。 您仍然必须手动将项目插入列表,然后才能以这种方式使用。

      更新: 当您处理大型集合时,这种重载可以帮助您,并且您几乎可以肯定您会将大约 N 个项目放入您的列表中。因此,您可以在创建时保留内存,并在将项目添加到此列表时避免内存分配(有时可能会很慢)。

      【讨论】:

      • 但这并不能真正回答问题。问题是,如果创建具有特定容量的列表,为什么不能访问该容量。初始化容量为 10 的数组实际上也不会向数组中添加任何内容,但您仍然可以在创建数组后立即显式分配每个元素。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-29
      相关资源
      最近更新 更多