什么是 IndexOutOfRangeException / ArgumentOutOfRangeException,我该如何解决?
Posted
技术标签:
【中文标题】什么是 IndexOutOfRangeException / ArgumentOutOfRangeException,我该如何解决?【英文标题】:What is an IndexOutOfRangeException / ArgumentOutOfRangeException and how do I fix it? 【发布时间】:2021-10-05 14:18:32 【问题描述】:我有一些代码,当它执行时,它会抛出一个IndexOutOfRangeException
,说,
索引超出了数组的范围。
这是什么意思,我能做些什么?
根据使用的类,它也可以是ArgumentOutOfRangeException
mscorlib.dll 中出现“System.ArgumentOutOfRangeException”类型的异常,但未在用户代码中处理其他信息:索引超出范围。必须为非负数且小于集合的大小。
【问题讨论】:
如果您只有 4 个项目,则在您的集合中,但代码试图获取索引 5 中的项目。这将引发 IndexOutOfRangeException。检查索引 = 5; if(items.Length >= index ) Console.WriteLine(intems[index ]); 【参考方案1】:这是什么?
此异常表示您尝试使用无效索引按索引访问集合项。当索引低于集合的下限或大于或等于它包含的元素数时,索引无效。
当它被抛出时
给定一个声明为的数组:
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
(例如,如果您在 Bitmap
类上使用 GetPixel
方法)。
数组不会增长
数组很快。与其他所有集合相比,线性搜索非常快。这是因为项目在内存中是连续的,因此可以计算内存地址(并且增量只是一个加法)。无需遵循节点列表,简单的数学运算!您为此付出了一个限制:它们不能增长,如果您需要更多元素,则需要重新分配该数组(如果必须将旧项目复制到新块,这可能需要相对较长的时间)。您使用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 中引入,无论如何这也是多年来使用的众所周知的约定) 如果他们没有找到任何东西。让我们假设您有一个与字符串相当的对象数组。你可能会想写这段代码:
// 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>
在数组使用 IndexOutOfRangeException
的相同情况下会抛出 ArgumentOutOfRangeException
。
与数组不同,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
,因为您的数据集只有两列,但您正试图从第三列获取值(索引始终从 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
和用.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 ));
验证参数
如果索引来自一个参数,您应该始终验证它们(抛出适当的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);
如果函数是私有的,您可以简单地将 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
如何调试
在我看来,关于这个错误的大多数问题都可以简单地避免。你花在写一个正确的问题上的时间(带有一个小的工作示例和一个小的解释)可能比你调试代码所需的时间要多得多。首先,阅读 Eric Lippert 关于debugging of small programs 的博文,我不会在这里重复他的话,但绝对是必读。
您有源代码,您有带有堆栈跟踪的异常消息。去那里,选择正确的行号,你会看到:
array[index] = newValue;
您发现了错误,请检查index
的增加情况。这样对吗?检查数组是如何分配的,是否与index
的增加一致?根据您的规格是否正确?如果您对所有这些问题的回答
一个好的起点是始终使用断言并验证输入。你甚至可能想要使用代码契约。当出现问题并且您无法通过快速查看代码来弄清楚发生了什么时,您必须求助于一位老朋友:调试器。只需在 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 规则
此异常表示您尝试使用无效索引按索引访问集合项。当索引低于集合的下限或大于等于它包含的元素数时,索引无效。数组声明中定义的最大允许索引
【讨论】:
【参考方案2】:关于什么是索引越界异常的简单解释:
想想那里有一列火车,它的车厢是 D1、D2、D3。 一位乘客上车,他有 D4 的车票。 现在会发生什么。乘客想进入一个不存在的车厢,所以显然会出现问题。
同样的场景:每当我们尝试访问数组列表等时,我们只能访问数组中现有的索引。 array[0]
和 array[1]
已存在。如果我们尝试访问array[3]
,它实际上并不存在,因此会出现索引越界异常。
【讨论】:
【参考方案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) 访问边界之外时,它会引发异常。
【讨论】:
【参考方案4】:从很长的完整接受答案的一个方面来看,与许多其他异常类型相比,IndexOutOfRangeException
有一个重要的点,那就是:
通常存在复杂的程序状态,可能难以在代码中的特定点进行控制,例如数据库连接断开,因此无法检索输入的数据等...此类问题通常会导致异常某种必须冒泡到更高水平的类型,因为它发生的地方在那个时候无法处理它。
IndexOutOfRangeException
通常不同,因为在大多数情况下,在引发异常的地方检查它是非常简单的。通常这种异常是由一些代码抛出的,这些代码可以很容易地在它发生的地方处理问题——只需检查数组的实际长度。您不想通过更高级别处理此异常来“修复”此问题 - 而是通过确保它不会在第一个实例中抛出 - 在大多数情况下,通过检查数组长度很容易做到这一点。
另一种说法是,由于真正缺乏对输入或程序状态的控制,可能会出现其他异常,但 IndexOutOfRangeException
通常只是飞行员(程序员)错误。
【讨论】:
【参考方案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. */
这背后的主要原因是编译器通常不会检查这些东西,因此它们只会在运行时表达自己。
【讨论】:
如果要访问的数组大小和/或索引直到运行时才知道,那么编译器无法检查索引是否有效。否则,这不会添加其他答案未涵盖的任何信息。 是的,但是在已知数组大小的情况下,编译器也不做任何事情。我相信至少在 C++、Java 和 C# 方面。以上是关于什么是 IndexOutOfRangeException / ArgumentOutOfRangeException,我该如何解决?的主要内容,如果未能解决你的问题,请参考以下文章