我有一些代码,执行时会抛出IndexOutOfRangeException
,说:
这是什么意思,我该怎么办?
根据使用的类,它也可以是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 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
类上使用Bitmap
方法)。数组不增加
数组很快。与所有其他集合相比,线性搜索非常快。这是因为项目在内存中是连续的,所以可以计算内存地址(增量只是一个加法)。无需遵循节点列表,简单的数学运算!您为此付出了一定的限制:它们无法增长,如果您需要更多的元素,则需要重新分配该数组(如果必须将旧项目复制到新块中,这可能会花费相对较长的时间)。您使用
Array.Resize<T>()
调整它们的大小,此示例将一个新条目添加到现有数组:Array.Resize(ref array, array.Length + 1);
不要忘记有效的索引是从
0
到Length - 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(.net 2.0中引入了nullable,无论如何它也是多年以来使用的众所周知的惯例)任何东西。假设您有一个与字符串可比的对象数组。您可能会考虑编写以下代码:
// 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>
的情况,ArgumentOutOfRangeException
会抛出IndexOutOfRangeException
。与数组不同,
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()
将引发IndexOutOfRangeException
,因为您的数据集只有两列,但是您试图从第3列中获取一个值(索引始终基于0)。请注意,此行为与大多数
IDataReader
实现(SqlDataReader
,OleDbDataReader
等)共享。如果使用索引器运算符的IDataReader重载(采用列名并传递无效的列名),也可能会遇到相同的异常。
例如,假设您检索了一个名为Column1的列,然后尝试使用以下方法检索该字段的值
var data = dr["Colum1"]; // Missing the n in Column1.
发生这种情况是因为实现了索引器运算符,试图检索不存在的Colum1字段的索引。当其内部帮助程序代码返回-1作为“Colum1”的索引时,GetOrdinal方法将引发此异常。
其他
引发此异常时,还有另一种(记录在案)的情况:如果在
DataView
中,提供给DataViewSort
属性的数据列名称无效。如何避免
在此示例中,为简单起见,让我假设数组始终是一维的并且基于0。如果您想严格一点(或者正在开发一个库),则可能需要用
0
替换GetLowerBound(0)
,用.Length
替换GetUpperBound(0)
(当然,如果您的参数类型为System.Arra
y,则不适用于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
,此函数的用户可能会期望这样做,因为他们正在传递数组,但这并不总是那么明显。我建议始终验证公共(public)功能的参数: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);
}
如果函数是私有(private)的,则可以简单地将
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的有关debugging of small programs的博客文章,在这里我不会重复他的话,但这绝对是必读的。
您具有源代码,具有堆栈跟踪的异常消息。去那里,选择正确的行号,您将看到:
array[index] = newValue;
您发现了错误,请检查
index
如何增加。这样对吗?检查数组的分配方式,是否与index
的增加方式一致?根据您的要求正确吗?如果您对所有这些问题的回答都是肯定的,那么您将在StackOverflow上找到很好的帮助,但请先自己检查一下。您将节省自己的时间!一个好的起点是始终使用断言并验证输入。您甚至可能想要使用代码契约(Contract)。当出现问题时,您无法通过快速查看代码来判断会发生什么,那么您必须求助于一个老 friend :调试器。只需在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规则
此异常表示您正在尝试使用无效索引按索引访问集合项。当索引小于集合的下限或大于等于它包含的元素数时,索引无效。数组声明中定义的最大允许索引
关于c# - 什么是IndexOutOfRangeException/ArgumentOutOfRangeException,如何解决?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/20940979/