什么是IndexOutOfRangeException / ArgumentOutOfRangeException,如何解决? [英] What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it?

查看:194
本文介绍了什么是IndexOutOfRangeException / ArgumentOutOfRangeException,如何解决?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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


索引在数组的边界之外。


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

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


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



解决方案

是什么?



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



被抛出时



给出一个声明为的数组:

  byte [] array = new byte [4]; 

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



数组长度

在C#中,数组通常基于0。这意味着第一个元素的索引为0,最后一个元素的索引为 Length-1 (其中 Length 是项目总数

  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 i = 0; j< data.GetLength(1); ++ j ){
data [i,j] = 1;
}
}

上限不包含 >

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

  Color [,]像素=新Color [imageWidth,imageHeight]; 
for(int x = 0; x< = imageWidth; ++ x){
for(int y = 0; y< = imageHeight; ++ y){
pixel [ x,y] = backgroundColor;
}
}

此代码将失败,因为数组基于0并且图像中的最后一个(右下)像素为 pixels [imageWidth-1,imageHeight-1]

  pixels [imageWidth,imageHeight] = Color.Black; 

在另一种情况下,您可能会得到 ArgumentOutOfRangeException 这段代码(例如,如果您在 Bitmap 类上使用 GetPixel 方法)。



数组不增长

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

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

别忘了有效索引来自 0 Length-1 。如果您只是尝试在 Length 处分配项目,则会得到 IndexOutOfRangeException (如果您认为这种行为可能会使您感到困惑它们的语法可能类似于其他集合的 Insert 方法)。



特殊具有自定义下界的数组

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

  var array = Array.CreateInstance(typeof(位元组),新的int [] {4},新的int [] {1}); 

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



错误的参数

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

 私有静态字符串[] RomanNumbers = 
新字符串[] { I, II, III, IV, V};

公共静态字符串Romanize(int number)
{
return RomanNumbers [number];
}

意外结果

此异常也可能由于其他原因而抛出:按照惯例,许多 search函数都将返回-1(.net 2.0中引入了nullable,无论如何,它也是许多人所使用的众所周知的惯例年),如果他们没有找到任何东西。假设您有一个与字符串可比的对象数组。您可能会想编写以下代码:

  //与字符串
可比较的项目Console.WriteLine(第一个项目等于调试为 {0}。,
myArray [Array.IndexOf(myArray, Debug)]));

//任意对象
Console.WriteLine(等于 Debug的第一项是'{0}'。,
myArray [Array.FindIndex(myArray,x => x.Type == Debug)]);

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



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

  static int [] CountOccurences(int最大值,IEnumerable< int>数字){ b $ b int []结果=新int [最大值+ 1]; //包括0个

foreach(整数形式的整数)
++ result [number];

返回结果;
}

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



如何应用于 List< T>



与数组相同的情况-有效索引的范围-0(列表的索引始终从0)到list。Count-访问此范围之外的元素将导致异常。



请注意,对于数组使用<$ c $的相同情况, List< T> 抛出 ArgumentOutOfRangeException c> IndexOutOfRangeException 。



与数组不同, List< T> 开始为空-因此

  var list = new List< int>(); 

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

  list [0] = 42; //例外
list.Add(42); //正确

IDataReader和Columns

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

 使用(var connection = CreateConnection()){
使用(var命令= connection.CreateCommand()){
command.CommandText = SELECT MyColumn1,MyColumn2 FROM MyTable;

使用(var reader = command.ExecuteReader()){
while(reader.Read()){
ProcessData(reader.GetString(2)); //抛出!
}
}
}
}

GetString()会抛出 IndexOutOfRangeException ,因为您的数据集只有两列,但是您试图从中获取值第3个(索引始终基于 0。)



请注意,大多数 IDataReader 实现( SqlDataReader OleDbDataReader 等)。



如果使用索引器运算符的IDataReader重载,该重载符采用列名并传递无效的列名,则也会得到相同的异常。

例如,假设您检索了一个名为 Column1 的列,然后尝试使用

  var data = dr [ Colum1]; //缺少Column1中的n。 

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



其他

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



如何避免



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

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

应这样重写:

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

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

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

void test(){
//这将引发InvalidCastException,无法转换Int32 []到Int32 [*]
foo((int)Array.CreateInstance(typeof(int),new int [] {1},new int [] {1})));
}

验证参数

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

  static void SetRange< T>(T [] array,int from ,int length,Func< i,T>函数)
{
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(来自> = 0&&来自< array.Length); 

检查对象状态

数组索引可能不是直接来自参数。它可能是对象状态的一部分。通常,验证对象状态(根据需要以及使用功能参数)始终是一种良好的做法。您可以使用 Debug.Assert()抛出一个适当的异常(对该问题进行更具描述性的处理)或像下面的示例那样处理:

  class表{
public int SelectedIndex {get;组; }
public Row [] Rows {get;组; }

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

//没有选择错误,在这里我们只为
返回null //这种情况(这可能是我们使用此属性
//而不是直接访问的原因)
if(SelectedIndex< 0 || SelectedIndex> = Rows.Length)
返回null;

return Rows [SelectedIndex];
}
}

验证返回值

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

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



如何调试



在我看来,关于此错误的大多数问题都可以简单地避免。您花费在编写适当的问题上的时间(带有一个简短的示例和一个简短的解释)所花费的时间可能比调试代码所花费的时间要容易得多。首先,请阅读Eric Lippert的博客文章,其中有关调试小型程序,我在这里不再重复他的话,但这绝对是必读



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

  array [index] = newValue; 

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



一个好的起点是始终使用断言并验证输入。您甚至可能想要使用代码合同。当出现问题时,如果您无法快速查看代码,就无法确定会发生什么,那么您就不得不求助于一个老朋友:调试器。只需在Visual Studio(或您最喜欢的IDE)中的调试中运行您的应用程序,您就会确切看到哪一行引发此异常,涉及到哪个数组以及您要使用哪个索引。确实,您有99%的时间会在几分钟内自行解决。



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



故事的VB.NET方面



我们在C#答案中说的所有内容都对VB.NET有效,但语法上存在明显差异,但是在处理VB.NET数组时要考虑一个重要点



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

 '声明一个空间为5个整数
'4的数组是从0到4的最大有效索引
Dim myArray(4)as Integer

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

 对于i作为整数= 0到4 
myArray(i)= i
下一个



VB.NET规则



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


I have some code and when it executes, it throws a IndexOutOfRangeException, saying,

Index was outside the bounds of the array.

What does this mean, and what can I do about it?

Depending on classes used it can also be ArgumentOutOfRangeException

An exception of type 'System.ArgumentOutOfRangeException' occurred in mscorlib.dll but was not handled in user code Additional information: Index was out of range. Must be non-negative and less than the size of the collection.

解决方案

What Is It?

This exception means that you're trying to access a collection item by index, using an invalid index. An index is invalid when it's lower than the collection's lower bound or greater than or equal to the number of elements it contains.

When It Is Thrown

Given an array declared as:

byte[] array = new byte[4];

You can access this array from 0 to 3, values outside this range will cause IndexOutOfRangeException to be thrown. Remember this when you create and access an array.

Array Length
In C#, usually, arrays are 0-based. It means that first element has index 0 and last element has index Length - 1 (where Length is total number of items in the array) so this code doesn't work:

array[array.Length] = 0;

Moreover please note that if you have a multidimensional array then you can't use Array.Length for both dimension, you have to use 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;
    }
}

Upper Bound Is Not Inclusive
In the following example we create a raw bidimensional array of Color. Each item represents a pixel, indices are from (0, 0) to (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;
    }
}

This code will then fail because array is 0-based and last (bottom-right) pixel in the image is pixels[imageWidth - 1, imageHeight - 1]:

pixels[imageWidth, imageHeight] = Color.Black;

In another scenario you may get ArgumentOutOfRangeException for this code (for example if you're using GetPixel method on a Bitmap class).

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

Don't forget that valid indices are from 0 to Length - 1. If you simply try to assign an item at Length you'll get IndexOutOfRangeException (this behavior may confuse you if you think they may increase with a syntax similar to Insert method of other collections).

Special Arrays With Custom Lower Bound
First item in arrays has always index 0. This is not always true because you can create an array with a custom lower bound:

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

In that example, array indices are valid from 1 to 4. Of course, upper bound cannot be changed.

Wrong Arguments
If you access an array using unvalidated arguments (from user input or from function user) you may get this error:

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

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

Unexpected Results
This exception may be thrown for another reason too: by convention, many search functions will return -1 (nullables has been introduced with .NET 2.0 and anyway it's also a well-known convention in use from many years) if they didn't find anything. Let's imagine you have an array of objects comparable with a string. You may think to write this code:

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

This will fail if no items in myArray will satisfy search condition because Array.IndexOf() will return -1 and then array access will throw.

Next example is a naive example to calculate occurrences of a given set of numbers (knowing maximum number and returning an array where item at index 0 represents number 0, items at index 1 represents number 1 and so on):

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

Of course, it's a pretty terrible implementation but what I want to show is that it'll fail for negative numbers and numbers above maximum.

How it applies to List<T>?

Same cases as array - range of valid indexes - 0 (List's indexes always start with 0) to list.Count - accessing elements outside of this range will cause the exception.

Note that List<T> throws ArgumentOutOfRangeException for the same cases where arrays use IndexOutOfRangeException.

Unlike arrays, List<T> starts empty - so trying to access items of just created list lead to this exception.

var list = new List<int>();

Common case is to populate list with indexing (similar to Dictionary<int, T>) will cause exception:

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

IDataReader and Columns
Imagine you're trying to read data from a database with this code:

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() will throw IndexOutOfRangeException because you're dataset has only two columns but you're trying to get a value from 3rd one (indices are always 0-based).

Please note that this behavior is shared with most IDataReader implementations (SqlDataReader, OleDbDataReader and so on).

You can get the same exception also if you use the IDataReader overload of the indexer operator that takes a column name and pass an invalid column name.
Suppose for example that you have retrieved a column named Column1 but then you try to retrieve the value of that field with

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

This happens because the indexer operator is implemented trying to retrieve the index of a Colum1 field that doesn't exist. The GetOrdinal method will throw this exception when its internal helper code returns a -1 as the index of "Colum1".

Others
There is another (documented) case when this exception is thrown: if, in DataView, data column name being supplied to the DataViewSort property is not valid.

How to Avoid

In this example, let me assume, for simplicity, that arrays are always monodimensional and 0-based. If you want to be strict (or you're developing a library), you may need to replace 0 with GetLowerBound(0) and .Length with GetUpperBound(0) (of course if you have parameters of type System.Array, it doesn't apply for T[]). Please note that in this case, upper bound is inclusive then this code:

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

Should be rewritten like this:

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

Please note that this is not allowed (it'll throw InvalidCastException), that's why if your parameters are T[] you're safe about custom lower bound arrays:

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

Validate Parameters
If index comes from a parameter you should always validate them (throwing appropriate ArgumentException or ArgumentOutOfRangeException). In the next example, wrong parameters may cause IndexOutOfRangeException, users of this function may expect this because they're passing an array but it's not always so obvious. I'd suggest to always validate parameters for public functions:

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 function is private you may simply replace if logic with Debug.Assert():

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

Check Object State
Array index may not come directly from a parameter. It may be part of object state. In general is always a good practice to validate object state (by itself and with function parameters, if needed). You can use Debug.Assert(), throw a proper exception (more descriptive about the problem) or handle that like in this example:

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

Validate Return Values
In one of previous examples we directly used Array.IndexOf() return value. If we know it may fail then it's better to handle that case:

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

How to Debug

In my opinion, most of the questions, here on SO, about this error can be simply avoided. The time you spend to write a proper question (with a small working example and a small explanation) could easily much more than the time you'll need to debug your code. First of all, read this Eric Lippert's blog post about debugging of small programs, I won't repeat his words here but it's absolutely a must read.

You have source code, you have exception message with a stack trace. Go there, pick right line number and you'll see:

array[index] = newValue;

You found your error, check how index increases. Is it right? Check how array is allocated, is coherent with how index increases? Is it right according to your specifications? If you answer yes to all these questions, then you'll find good help here on StackOverflow but please first check for that by yourself. You'll save your own time!

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.

If this happens in production then you'd better to add assertions in incriminated code, probably we won't see in your code what you can't see by yourself (but you can always bet).

The VB.NET side of the story

Everything that we have said in the C# answer is valid for VB.NET with the obvious syntax differences but there is an important point to consider when you deal with VB.NET arrays.

In VB.NET, arrays are declared setting the maximum valid index value for the array. It is not the count of the elements that we want to store in the array.

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

So this loop will fill the array with 5 integers without causing any IndexOutOfRangeException

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

The VB.NET rule

This exception means that you're trying to access a collection item by index, using an invalid index. An index is invalid when it's lower than the collection's lower bound or greater than equal to the number of elements it contains. the maximum allowed index defined in the array declaration

这篇关于什么是IndexOutOfRangeException / ArgumentOutOfRangeException,如何解决?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆