什么是 IndexOutOfRangeException / ArgumentOutOfRangeException,如何解决?

What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?

提问人:Adriano Repetti 提问时间:1/6/2014 最后编辑:Alexei LevenkovAdriano Repetti 更新时间:11/14/2022 访问量:333535

问:

我有一些代码,当它执行时,它会抛出一个 ,说,IndexOutOfRangeException

索引超出了数组的边界。

这是什么意思,我能做些什么?

根据使用的类,它也可以是ArgumentOutOfRangeException

mscorlib.dll 中发生类型为“System.ArgumentOutOfRangeException”的异常,但未在用户代码中处理 其他信息: 索引超出范围。必须为非负数且小于集合的大小。

C# .NET IndexOutOfRangeException

评论

0赞 Babu Kumarasamy 6/29/2020
在您的集合中,如果您只有 4 个项目,但代码试图获取索引 5 中的项目。这将引发 IndexOutOfRangeException。校验指数 = 5;if(items.长度 >= index ) Console.WriteLine(intems[index ]);

答:

275赞 20 revs, 6 users 82%Adriano Repetti #1

这是什么?

此异常意味着您尝试使用无效的索引按索引访问集合项。当索引低于集合的下限或大于或等于它所包含的元素数时,索引无效。

当它被抛出时

给定一个声明为的数组:

byte[] array = new byte[4];

您可以从 0 到 3 访问此数组,超出此范围的值将导致抛出。在创建和访问数组时,请记住这一点。IndexOutOfRangeException

数组长度
在 C# 中,数组通常从 0 开始。这意味着第一个元素的索引为 0,最后一个元素的索引为 0(其中是数组中的项目总数),因此此代码不起作用:
Length - 1Length

array[array.Length] = 0;

此外,请注意,如果你有一个多维数组,那么你不能同时用于两个维度,你必须使用:Array.LengthArray.GetLength()

int[,] data = new int[10, 5];
for (int i=0; i < data.GetLength(0); ++i) {
    for (int j=0; j < data.GetLength(1); ++j) {
        data[i, j] = 1;
    }
}

上限不包括在下面的示例中,
我们创建一个原始的二维数组。每个项目代表一个像素,索引是从到 。
Color(0, 0)(imageWidth - 1, imageHeight - 1)

Color[,] pixels = new Color[imageWidth, imageHeight];
for (int x = 0; x <= imageWidth; ++x) {
    for (int y = 0; y <= imageHeight; ++y) {
        pixels[x, y] = backgroundColor;
    }
}

然后,此代码将失败,因为数组是从 0 开始的,并且图像中的最后一个(右下角)像素是:pixels[imageWidth - 1, imageHeight - 1]

pixels[imageWidth, imageHeight] = Color.Black;

在另一种情况下,您可能会获得此代码(例如,如果您在类上使用方法)。ArgumentOutOfRangeExceptionGetPixelBitmap

阵列不增长
阵列速度很快。与其他所有集合相比,线性搜索速度非常快。这是因为项目在内存中是连续的,所以可以计算内存地址(增量只是一个加法)。无需遵循节点列表,简单的数学运算!你支付这笔费用有一个限制:它们不能增长,如果你需要更多的元素,你需要重新分配该数组(如果必须将旧项目复制到新块,这可能需要相对较长的时间)。您可以使用 调整它们的大小,此示例将新条目添加到现有数组中:
Array.Resize<T>()

Array.Resize(ref array, array.Length + 1);

不要忘记,有效索引是 from to 。如果你只是尝试在 你会得到一个项目(如果你认为它们可能会增加类似于其他集合的方法的语法,这种行为可能会让你感到困惑)。0Length - 1LengthIndexOutOfRangeExceptionInsert

具有自定义下限
的特殊数组 数组中的第一项索引始终为 0。这并不总是正确的,因为您可以创建具有自定义下限的数组:

var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });

在该示例中,数组索引的有效期为 1 到 4。当然,上限是不能改变的。

错误的参数 如果使用未经验证的参数
(来自用户输入或函数用户)访问数组,则可能会出现以下错误:

private static string[] RomanNumbers =
    new string[] { "I", "II", "III", "IV", "V" };

public static string Romanize(int number)
{
    return RomanNumbers[number];
}

意外结果
此异常也可能由于另一个原因而引发:按照惯例,如果许多搜索函数没有找到任何内容,它们将返回 -1(.NET 2.0 中引入了 nullables,无论如何,这也是多年来使用的众所周知的约定)。假设您有一个与字符串相当的对象数组。你可能会想到写这段代码:

// Items comparable with a string
Console.WriteLine("First item equals to 'Debug' is '{0}'.",
    myArray[Array.IndexOf(myArray, "Debug")]);

// Arbitrary objects
Console.WriteLine("First item equals to 'Debug' is '{0}'.",
    myArray[Array.FindIndex(myArray, x => x.Type == "Debug")]);

如果 中没有项目满足搜索条件,这将失败,因为将返回 -1,然后数组访问将抛出。myArrayArray.IndexOf()

下一个示例是一个幼稚的例子,用于计算一组给定数字的出现次数(知道最大数字并返回一个数组,其中索引 0 处的项目表示数字 0,索引 1 处的项目表示数字 1,依此类推):

static int[] CountOccurences(int maximum, IEnumerable<int> numbers) {
    int[] result = new int[maximum + 1]; // Includes 0

    foreach (int number in numbers)
        ++result[number];

    return result;
}

当然,这是一个非常糟糕的实现,但我想展示的是,对于负数和上面的数字,它会失败。maximum

它如何应用于 List<T>

与数组 - 有效索引范围 - 0(索引始终以 0 开头)到 - 访问超出此范围的元素将导致异常的情况相同。Listlist.Count

请注意,对于数组使用 .List<T>ArgumentOutOfRangeExceptionIndexOutOfRangeException

与数组不同,从空开始 - 因此尝试访问刚刚创建的列表的项目会导致此异常。List<T>

var list = new List<int>();

常见的情况是用索引填充列表(类似于 )会导致异常:Dictionary<int, T>

list[0] = 42; // exception
list.Add(42); // correct

IDataReader 和列
假设您正在尝试使用以下代码从数据库中读取数据:

using (var connection = CreateConnection()) {
    using (var command = connection.CreateCommand()) {
        command.CommandText = "SELECT MyColumn1, MyColumn2 FROM MyTable";

        using (var reader = command.ExecuteReader()) {
            while (reader.Read()) {
                ProcessData(reader.GetString(2)); // Throws!
            }
        }
    }
}

GetString()会抛出,因为你的数据集只有两列,但你试图从第三列获取一个值(索引始终从 0 开始)。IndexOutOfRangeException

请注意,此行为与大多数实现(等)共享。IDataReaderSqlDataReaderOleDbDataReader

如果使用索引器运算符的 IDataReader 重载,则也会获得相同的异常,该运算符采用列名并传递无效的列名。
例如,假设您检索了一个名为 Column1 的列,但随后您尝试检索该字段的值

 var data = dr["Colum1"];  // Missing the n in Column1.

发生这种情况的原因是,实现索引器运算符时尝试检索不存在的 Colum1 字段的索引。当 GetOrdinal 方法的内部帮助程序代码返回 -1 作为“Colum1”的索引时,将引发此异常。

其他
抛出此异常时还有另一种(记录在案的)情况:如果在 中提供给属性的数据列名无效。
DataViewDataViewSort

如何避免

在此示例中,为简单起见,我假设数组始终是一维的且从 0 开始。如果你想严格(或者你正在开发一个库),你可能需要替换 with 和 with(当然,如果你有 y 类型的参数,它不适用于 )。请注意,在这种情况下,上限是包含以下代码的:0GetLowerBound(0).LengthGetUpperBound(0)System.ArraT[]

for (int i=0; i < array.Length; ++i) { }

应该像这样重写:

for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }

请注意,这是不允许的(它会抛出),这就是为什么如果你的参数是,你对自定义下限数组是安全的:InvalidCastExceptionT[]

void foo<T>(T[] array) { }

void test() {
    // This will throw InvalidCastException, cannot convert Int32[] to Int32[*]
    foo((int)Array.CreateInstance(typeof(int), new int[] { 1 }, new int[] { 1 }));
}

验证参数:
如果索引来自参数,则应始终验证它们(抛出适当的 或 )。在下一个示例中,错误的参数可能会导致 ,此函数的用户可能会期望这样做,因为他们正在传递数组,但这并不总是那么明显。我建议始终验证公共函数的参数:
ArgumentExceptionArgumentOutOfRangeExceptionIndexOutOfRangeException

static void SetRange<T>(T[] array, int from, int length, Func<i, T> function)
{
    if (from < 0 || from>= array.Length)
        throw new ArgumentOutOfRangeException("from");

    if (length < 0)
        throw new ArgumentOutOfRangeException("length");

    if (from + length > array.Length)
        throw new ArgumentException("...");

    for (int i=from; i < from + length; ++i)
        array[i] = function(i);
}

如果函数是私有的,你可以简单地将逻辑替换为:ifDebug.Assert()

Debug.Assert(from >= 0 && from < array.Length);

检查对象状态
数组索引不能直接来自参数。它可能是对象状态的一部分。通常,验证对象状态始终是一种很好的做法(如果需要,可以单独验证对象状态并使用函数参数验证对象状态)。您可以使用 抛出适当的异常(对问题更具描述性)或处理它,如以下示例所示:
Debug.Assert()

class Table {
    public int SelectedIndex { get; set; }
    public Row[] Rows { get; set; }

    public Row SelectedRow {
        get {
            if (Rows == null)
                throw new InvalidOperationException("...");

            // No or wrong selection, here we just return null for
            // this case (it may be the reason we use this property
            // instead of direct access)
            if (SelectedIndex < 0 || SelectedIndex >= Rows.Length)
                return null;

            return Rows[SelectedIndex];
        }
}

验证返回值 在前面的一个示例中,我们直接使用了返回值
。如果我们知道它可能会失败,那么最好处理这种情况:
Array.IndexOf()

int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }

如何调试

在我看来,关于这个错误的大多数问题,在SO上,都可以简单地避免。你花在写一个合适的问题上的时间(有一个小的工作示例和一个小的解释)可能比你调试代码所需的时间要多得多。首先,阅读 Eric Lippert 关于小程序调试的这篇博文,我不会在这里重复他的话,但它绝对是必读的。

你有源代码,你有带有堆栈跟踪的异常消息。转到那里,选择正确的行号,您将看到:

array[index] = newValue;

您发现了错误,请检查如何增加。这是对的吗?检查数组是如何分配的,是否与如何增加一致?它是否符合您的规格?如果您对所有这些问题的回答都是肯定的,那么您会在 StackOverflow 上找到很好的帮助,但请先自己检查一下。您将节省自己的时间!indexindex

一个好的起点是始终使用断言并验证输入。您甚至可能希望使用代码协定。当出现问题并且您无法通过快速查看代码来弄清楚会发生什么时,您必须求助于一个老朋友:调试器。只需在 Visual Studio(或你喜欢的 IDE)中调试运行应用程序,你就会确切地看到哪一行引发了此异常、涉及哪个数组以及你尝试使用哪个索引。真的,99%的时间你会在几分钟内自己解决。

如果这种情况发生在生产环境中,那么你最好在有罪的代码中添加断言,可能我们不会在你的代码中看到你自己看不到的东西(但你总是可以打赌)。

故事的 VB.NET 面

我们在 C# 答案中所说的一切都适用于具有明显语法差异的 VB.NET,但在处理 VB.NET 数组时需要考虑一个重要的点。

在 VB.NET 中,声明数组是为了设置数组的最大有效索引值。它不是我们要存储在数组中的元素计数。

' declares an array with space for 5 integer 
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer

因此,此循环将用 5 个整数填充数组,而不会导致任何 IndexOutOfRangeException

For i As Integer = 0 To 4
    myArray(i) = i
Next

VB.NET 规则

此异常意味着您尝试使用无效的索引按索引访问集合项。当索引低于集合的下限或大于等于其包含的元素数时,索引无效。 数组声明中定义的最大允许索引

27赞 Lijo 3/4/2016 #2

关于什么是索引越界异常的简单说明:

试想一下,有一列火车在那里,它的车厢是D1,D2,D3。 一位乘客来上火车,他有D4的车票。 现在会发生什么。乘客想进入一个不存在的车厢,所以很明显会出现问题。

同样的场景:每当我们尝试访问数组列表等时,我们只能访问数组中的现有索引。 并且已经存在。如果我们尝试访问 ,它实际上并不存在,因此会出现索引越界异常。array[0]array[1]array[3]

18赞 sonertbnc 12/7/2017 #3

为了便于理解问题,假设我们编写了以下代码:

static void Main(string[] args)
{
    string[] test = new string[3];
    test[0]= "hello1";
    test[1]= "hello2";
    test[2]= "hello3";

    for (int i = 0; i <= 3; i++)
    {
        Console.WriteLine(test[i].ToString());
    }
}

结果将是:

hello1
hello2
hello3

Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.

数组的大小为 3(索引 0、1 和 2),但 for 循环循环 4 次(0、1、2 和 3)。
因此,当它尝试使用 (3) 的边界之外进行访问时,它会抛出异常。

6赞 Ricibob 3/1/2019 #4

与许多其他例外类型相比,从很长的完整接受答案中可以看出一个重要的点,那就是:IndexOutOfRangeException

通常,在代码中的特定点可能难以控制复杂的程序状态,例如,数据库连接断开,因此无法检索输入数据等。这种问题通常会导致某种异常,该异常必须上升到更高的级别,因为发生时无法处理它。

IndexOutOfRangeException通常不同,因为在大多数情况下,在引发异常时检查是非常微不足道的。通常,这种异常是由一些代码引发的,这些代码可以很容易地在问题发生的地方处理问题 - 只需检查数组的实际长度即可。你不想通过在更高的位置处理这个异常来“修复”这个问题 - 而是通过确保它不会在第一个实例中被抛出 - 在大多数情况下,这很容易通过检查数组长度来实现。

另一种说法是,由于真正缺乏对输入或程序状态的控制,可能会出现其他异常,但通常只是试点(程序员)错误。IndexOutOfRangeException

3赞 Armin Fisher 2/19/2022 #5

这两个例外在各种编程语言中很常见,正如其他人所说,当您访问索引大于数组大小的元素时。例如:

var array = [1,2,3];
/* var lastElement = array[3] this will throw an exception, because indices 
start from zero, length of the array is 3, but its last index is 2. */ 

这背后的主要原因是编译器通常不会检查这些东西,因此它们只会在运行时表达自己。

与此类似:为什么现代编译器无法捕获对数组进行越界访问的尝试?

评论

0赞 Lance U. Matthews 2/19/2022
如果要访问的数组大小和/或索引在运行时之前是未知的,则编译器无法检查索引是否有效。否则,这不会添加其他答案未涵盖的任何信息。
0赞 Armin Fisher 2/19/2022
是的,但是在已知数组大小的情况下,编译器也不执行任何操作。我相信,至少在 C++、Java 和 C# 中是这样。