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


189

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

指数数组的边界之外。

这是什么意思,我该怎么办?

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

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


在您的集合中,如果只有4个项目,但是代码尝试在索引5中获得一个项目。这将引发IndexOutOfRangeException。检查索引= 5; if(items.Length> = index)Console.WriteLine(intems [index]);
Babu Kumarasamy

Answers:


229

它是什么?

此异常表示您正在尝试使用无效索引按索引访问集合项。当索引小于集合的下限或大于或等于它包含的元素数时,索引无效。

当它被扔

给定一个声明为的数组:

byte[] array = new byte[4];

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

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

array[array.Length] = 0;

此外,请注意,如果您具有多维数组,则不能同时使用Array.Length两个维度,而必须使用Array.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;

在另一种情况下,您可能会获得ArgumentOutOfRangeException此代码(例如,如果您在类GetPixel上使用method Bitmap)。

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

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

别忘了有效索引是从0Length - 1。如果您只是简单地尝试分配一个项目就Length可以了IndexOutOfRangeException(如果您认为这些项目可能会以类似于Insert其他集合方法的语法增加,那么这种行为可能会使您感到困惑)。

具有自定义下界的特殊数组数组中的
第一项始终具有索引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(nullables已在.NET 2.0中引入,无论如何它也是多年来使用的众所周知的惯例)。什么也找不到。假设您有一个与字符串可比的对象数组。您可能会想编写以下代码:

// 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")]);

如果其中没有项目myArray满足搜索条件Array.IndexOf(),则此操作将失败,因为它将返回-1,然后将引发数组访问。

下一个示例是一个幼稚的示例,用于计算给定的一组数字(知道最大数字并返回一个数组,其中索引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(List索引始终以0开头)至list.Count-访问此范围之外的元素将导致异常。

请注意,在数组使用的相同情况下List<T>会抛出该异常。ArgumentOutOfRangeExceptionIndexOutOfRangeException

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

var list = new List<int>();

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

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

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

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()将会抛出异常,IndexOutOfRangeException因为您的数据集只有两列,但是您试图从第3列中获取一个值(索引始终基于0)。

请注意,此行为与大多数IDataReader实现(SqlDataReaderOleDbDataReader依此类推)共享。

如果使用索引器运算符的IDataReader重载(采用列名并传递无效的列名),也可能会遇到相同的异常。
例如,假设您检索了一个名为Column1的列,然后尝试使用以下方法检索该字段的值

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

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

其他
当引发此异常时,还有另一种(记录在案)的情况:如果in DataView中提供给DataViewSort属性的数据列名称无效。

如何避免

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

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

应该这样重写:

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

请注意,这是不允许的(它将抛出InvalidCastException),这就是为什么如果您的参数T[]对自定义下限数组安全的原因:

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 }));
}

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

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);
}

如果函数是私有的,则可以简单地将if逻辑替换为Debug.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;

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

一个好的起点是始终使用断言并验证输入。您甚至可能想要使用代码合同。当出现问题时,您无法通过快速查看代码来判断会发生什么,那么您就不得不求助于老朋友:debugger。只需在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规则

此异常表示您正在尝试使用无效索引按索引访问集合项。当索引小于集合的下限或大于集合的下限时,索引无效等于它包含的元素数量。 数组声明中定义的最大允许索引


19

关于什么是索引超出范围的异常的简单说明:

试想一列火车,那里的车厢是D1,D2,D3。一位乘客进入火车,他有D4的票。现在会发生什么。乘客想要进入一个不存在的车厢,因此显然会出现问题。

同样的情况:每当我们尝试访问数组列表等时,我们只能访问数组中的现有索引。array[0]并且array[1]存在。如果我们尝试访问array[3],则实际上它不存在,因此将出现索引超出范围的异常。


10

为了轻松理解问题,想象一下我们编写了以下代码:

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)进行访问时,将引发异常。


1

与一个很长的完整接受的答案IndexOutOfRangeException相比,与许多其他异常类型相比,还有一个重要的观点,那就是:

通常,程序状态复杂,可能难以控制代码中的特定点,例如,数据库连接断开,因此无法检索输入数据等。这种问题通常会导致某种异常,必须冒出一个更高的水平,因为它发生的地方当时无法解决。

IndexOutOfRangeException通常是不同的,因为在大多数情况下,在引发异常的位置进行检查非常简单。通常,这种异常会被某些代码抛出,这些代码可以很容易地在发生问题的地方进行处理-只需检查数组的实际长度即可。您不希望通过更高级别处理此异常来“解决”此问题,而是通过确保不在第一实例中抛出该异常来解决此问题,在大多数情况下,通过检查数组长度即可轻松实现。

换一种说法是,由于对输入或程序状态BUT的真正缺乏控制而导致其他异常的发生可能IndexOutOfRangeException不仅仅是导频(编程器)错误。

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.