它是什么?
此异常意味着您尝试使用无效索引通过索引访问集合项。当索引低于集合的下限或大于或等于其包含的元素数时,索引无效。
当它被抛出时
给定一个声明为的数组:
byte[] array = new byte[4];
您可以从 0 到 3 访问该数组,超出此范围的值将导致IndexOutOfRangeException
被扔掉。创建和访问数组时请记住这一点。
数组长度
在 C# 中,数组通常是从 0 开始的。这意味着第一个元素的索引为 0,最后一个元素的索引为Length - 1
(where 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)
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;
}
}
此代码将失败,因为数组是从 0 开始的,并且图像中的最后一个(右下角)像素是pixels[imageWidth - 1, imageHeight - 1]
:
pixels[imageWidth, imageHeight] = Color.Black;
在另一种情况下你可能会得到ArgumentOutOfRangeException
对于此代码(例如,如果您使用GetPixel
上的方法Bitmap
class).
数组不会增长
数组速度很快。与其他所有集合相比,线性搜索速度非常快。这是因为项目在内存中是连续的,因此可以计算内存地址(增量只是加法)。无需遵循节点列表,简单的数学运算!您付出的代价是有限制的:它们不能增长,如果您需要更多元素,您需要重新分配该数组(如果必须将旧项目复制到新块,这可能需要相对较长的时间)。您可以使用以下命令调整它们的大小Array.Resize<T>()
,此示例向现有数组添加一个新条目:
Array.Resize(ref array, array.Length + 1);
不要忘记有效索引来自0
to Length - 1
。如果您只是尝试分配一个项目Length
你会得到IndexOutOfRangeException
(如果您认为它们可能会使用类似于以下的语法增加,那么这种行为可能会让您感到困惑Insert
其他集合的方法)。
Special具有自定义下界的数组
数组中的第一项始终索引为 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> http://msdn.microsoft.com/en-us/library/6sh2ey19%28v=vs.110%29.aspx?
与数组相同的情况 - 有效索引范围 - 0 (List
的索引始终以 0) 开头list.Count
- 访问此范围之外的元素将导致异常。
注意List<T>
throws 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
因为你的数据集只有两列,但你试图从第三列获取一个值(索引是always从 0 开始)。
请注意,大多数人都有这种行为IDataReader
实施(SqlDataReader
, OleDbDataReader
等等)。
如果您使用索引器运算符的 IDataReader 重载(该运算符采用列名并传递无效的列名),您也会遇到相同的异常。
例如,假设您检索了名为Column1但随后您尝试使用以下命令检索该字段的值
var data = dr["Colum1"]; // Missing the n in Column1.
发生这种情况是因为索引器运算符被实现为尝试检索某个对象的索引Colum1不存在的字段。当 GetOrdinal 方法的内部帮助程序代码返回 -1 作为“Colum1”的索引时,该方法将引发此异常。
Others
当抛出此异常时,还有另一种(已记录的)情况:如果,在DataView
,数据列名称被提供给DataViewSort
属性无效。
如何避免
在此示例中,为简单起见,让我假设数组始终是单维且从 0 开始的。如果你想严格一些(或者你正在开发一个库),你可能需要替换0
with GetLowerBound(0)
and .Length
with 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
or 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 的博客文章:小程序的调试 http://ericlippert.com/2014/03/05/how-to-debug-small-programs/,我不会在这里重复他的话,但这绝对是一个必读.
您有源代码,您有带有堆栈跟踪的异常消息。去那里,选择正确的行号,你会看到:
array[index] = newValue;
您发现了错误,请检查如何解决index
增加。这样对吗?检查数组的分配方式,与分配方式是否一致index
增加?是否符合您的规格?如果你回答yes对于所有这些问题,您可以在 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 个整数填充数组,而不会导致任何索引超出范围异常
For i As Integer = 0 To 4
myArray(i) = i
Next
VB.NET 规则
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