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

索引超出了数组的边界。

这意味着什么?我能做些什么?

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

System类型的异常。在mscorlib.dll中出现了ArgumentOutOfRangeException,但在用户代码中没有处理。附加信息:索引超出范围。必须非负且小于集合的大小。


这是什么?

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

当它被扔出去时

给定一个数组声明为:

byte[] array = new byte[4];

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

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

array[array.Length] = 0;

此外,请注意,如果你有一个多维数组,那么你不能使用数组。对于两个维度的长度,你必须使用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;
    }
}

上界不包含 在下面的例子中,我们创建一个原始的二维颜色数组。每一项代表一个像素,索引从(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的,并且图像中的最后一个(右下)像素是像素[imageWidth - 1, imageHeight - 1]:

pixels[imageWidth, imageHeight] = Color.Black;

在另一个场景中,你可能会得到这段代码的ArgumentOutOfRangeException(例如,如果你在位图类上使用GetPixel方法)。

Arrays Do Not Grow An array is fast. Very fast in linear search compared to every other collection. It is because items are contiguous in memory so memory address can be calculated (and increment is just an addition). No need to follow a node list, simple math! You pay this with a limitation: they can't grow, if you need more elements you need to reallocate that array (this may take a relatively long time if old items must be copied to a new block). You resize them with Array.Resize<T>(), this example adds a new entry to an existing array:

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

不要忘记有效索引是从0到Length - 1。如果您只是尝试以长度分配一个项,则会得到indexoutofranceexception(如果您认为它们可能会使用类似于其他集合的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处的item表示数字0,索引1处的item表示数字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;
}

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

它如何应用于List<T>?

与array -有效索引的范围相同- 0 (List的索引总是以0开头)到List。计数-访问超出此范围的元素将导致异常。

注意List<T>在数组使用IndexOutOfRangeException的相同情况下抛出ArgumentOutOfRangeException。

与数组不同,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,因为你的数据集只有两列,但你试图从第三列获得一个值(索引总是基于0)。

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

如果使用索引操作符的IDataReader重载(该操作符接受列名并传递无效的列名),也会得到相同的异常。 例如,假设您检索了一个名为Column1的列,但是您尝试使用

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

这是因为索引器操作符的实现是试图检索不存在的Colum1字段的索引。当GetOrdinal方法的内部助手代码返回一个-1作为“Colum1”的索引时,该方法将抛出此异常。

其他人 在抛出此异常时,还有另一种(文档记载的)情况:在DataView中,如果提供给DataViewSort属性的数据列名无效。

如何避免

在这个例子中,为了简单起见,让我假设数组总是一维的和基于0的。如果你想要更严格(或者你正在开发一个库),你可能需要用GetLowerBound(0)替换0,用GetUpperBound(0)替换. length(当然,如果你的参数类型是System。数组,它不适用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 }));
}

验证参数 如果index来自一个参数,你应该总是验证它们(抛出适当的ArgumentException或ArgumentOutOfRangeException)。在下一个例子中,错误的参数可能会导致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);
}

如果function是私有的,你可以简单地用Debug.Assert()替换If逻辑:

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 { }

如何调试

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

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

array[index] = newValue;

你发现你的错误,检查索引如何增加。对吗?检查数组如何分配,是一致的索引如何增加?你方的规格对吗?如果你对这些问题的回答都是肯定的,那么你会在StackOverflow上找到很好的帮助,但请先自己检查一下。你可以节省自己的时间!

A good start point is to always use assertions and to validate inputs. You may even want to use code contracts. When something went wrong and you can't figure out what happens with a quick look at your code then you have to resort to an old friend: debugger. Just run your application in debug inside Visual Studio (or your favorite IDE), you'll see exactly which line throws this exception, which array is involved and which index you're trying to use. Really, 99% of the times you'll solve it by yourself in a few minutes.

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

VB。NET方面的故事

我们在c#回答中所说的一切都适用于VB。NET有明显的语法差异,但有一个重要的一点要考虑,当你处理VB。净数组。

在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个整数填充数组,而不会引起任何indexoutofranceexception

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

VB。净的规则

此异常意味着您正在尝试使用无效索引通过索引访问集合项。当索引小于集合的下界或大于等于它所包含的元素数时,索引无效。数组声明中定义的最大允许索引


关于什么是Index out of bound异常的简单解释:

假设有一列火车它的车厢是D1 D2 D3。 一名乘客进了车,他有D4的车票。 现在会发生什么。乘客想要进入一个不存在的车厢,显然会出现问题。

同样的场景:当我们试图访问一个数组列表时,我们只能访问数组中现有的索引。存在数组[0]和[1]。如果我们试图访问数组[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)边界外时抛出异常。


从很长很完整的公认答案来看,与许多其他异常类型相比,indexoutofranceexception有一个重要的观点,那就是:

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

IndexOutOfRangeException is generally different in that it in most cases it is pretty trivial to check for at the point where the exception is being raised. Generally this kind of exception get thrown by some code that could very easily deal with the issue at the place it is occurring - just by checking the actual length of the array. You don't want to 'fix' this by handling this exception higher up - but instead by ensuring its not thrown in the first instance - which in most cases is easy to do by checking the array length.

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


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

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. */ 

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

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