我有一些代码,当它执行时,它会抛出一个IndexOutOfRangeException
,说,
指数数组的边界之外。
这是什么意思,我能做些什么呢?
根据使用的类别,它也可以是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 j=0; j < data.GetLength(1); ++j) {
data[i, j] = 1;
}
}
上限不包含在内
在下面的示例中,我们创建了一个Color
的原始二维数组。每个项目代表一个像素,索引从qazxswpoi到(0, 0)
。
(imageWidth - 1, imageHeight - 1)
此代码将失败,因为数组是从0开始的,而图像中的最后一个(右下角)像素是Color[,] pixels = new Color[imageWidth, imageHeight];
for (int x = 0; x <= imageWidth; ++x) {
for (int y = 0; y <= imageHeight; ++y) {
pixels[x, y] = backgroundColor;
}
}
:
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
方法,则此行为可能会让您感到困惑)。
具有自定义下界的特殊阵列 数组中的第一项始终为索引0.这并非总是如此,因为您可以创建具有自定义下限的数组:
Insert
在该示例中,数组索引从1到4有效。当然,上限不能改变。
错误的论点 如果使用未经验证的参数(来自用户输入或来自函数用户)访问数组,则可能会收到此错误:
var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });
出乎意料的结果 此异常也可能由于另一个原因而被抛出:按照惯例,如果他们没有找到任何东西,许多搜索函数将返回-1(在.NET 2.0中引入了nullables,无论如何它也是多年来使用的众所周知的约定) 。让我们假设你有一个与字符串相当的对象数组。您可能会想要编写此代码:
private static string[] RomanNumbers =
new string[] { "I", "II", "III", "IV", "V" };
public static string Romanize(int number)
{
return RomanNumbers[number];
}
如果// 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
将返回-1然后数组访问将抛出。
下一个示例是一个简单的例子,用于计算给定数字集的出现次数(知道最大数量并返回一个数组,其中索引0处的项表示数字0,索引1处的项表示数字1,依此类推):
Array.IndexOf()
当然这是一个相当糟糕的实现,但我想要表明的是,它会因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
?
与数组相同的情况 - 有效索引的范围 - 0(List<T>
的索引始终以0开头)到List
- 访问此范围之外的元素将导致异常。
请注意,list.Count
会针对数组使用List<T>
的相同情况抛出ArgumentOutOfRangeException
。
与数组不同,IndexOutOfRangeException
开始为空 - 因此尝试访问刚创建的列表的项目会导致此异常。
List<T>
常见的情况是使用索引填充列表(类似于var list = new List<int>();
)将导致异常:
Dictionary<int, T>
IDataReader和Columns 想象一下,您正尝试使用以下代码从数据库中读取数据:
list[0] = 42; // exception
list.Add(42); // correct
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()
,因为你的数据集只有两列但是你试图从第三列获得一个值(索引总是从0开始)。
请注意,此行为与大多数IndexOutOfRangeException
实现(IDataReader
,SqlDataReader
等)共享。
如果使用带有列名并传递无效列名的索引器运算符的IDataReader重载,也可以获得相同的异常。 例如,假设您已检索到名为Column1的列,但随后尝试使用该字段检索该字段的值
OleDbDataReader
发生这种情况是因为实现了索引器运算符,试图检索不存在的Colum1字段的索引。当其内部帮助程序代码返回-1作为“Colum1”的索引时,GetOrdinal方法将抛出此异常。
其他
抛出此异常时还有另一个(记录的)情况:如果在 var data = dr["Colum1"]; // Missing the n in Column1.
中,提供给DataView
属性的数据列名无效。
在这个例子中,让我假设,为简单起见,数组总是单维的和基于0的。如果你想严格(或者你正在开发一个库)你可能需要用DataViewSort
替换0
和GetLowerBound(0)
.Length
(当然如果你有GetUpperBound(0)
y类型的参数,它不适用于System.Arra
)。请注意,在这种情况下,上限是包含,然后此代码:
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()
在我看来,关于这个错误的大多数问题都可以简单地避免。花在编写正确问题上的时间(使用一个小的工作示例和一个小的解释)可能比您需要调试代码的时间要多得多。首先阅读Eric Lippert关于int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
的博客文章,我不会在这里重复他的话,但绝对必须阅读。
你有源代码,你有带栈跟踪的异常消息。去那里,选择正确的行号,你会看到:
debugging of small programs
你找到了错误,检查array[index] = newValue;
如何增加。这样对吗?检查数组是如何分配的,与index
如何增加一致?根据你的指定是否正确?如果您对所有这些问题的回答是肯定的,那么您将在StackOverflow上找到很好的帮助,但请首先自行检查。你会节省自己的时间!
一个好的起点是始终使用断言并验证输入。您甚至可能希望使用代码合同。当出现问题并且您无法弄清楚快速查看代码时会发生什么,那么您必须求助于一位老朋友:调试器。只需在Visual Studio(或您最喜欢的IDE)中的调试中运行您的应用程序,您就会看到确切的哪一行抛出此异常,涉及哪个数组以及您尝试使用哪个索引。真的,99%的时间你会在几分钟内自己解决它。
如果这在生产中发生,那么你最好在有罪的代码中添加断言,可能我们不会在你的代码中看到你自己看不到的东西(但你总是可以下注)。
我们在C#中所说的一切对于VB.NET都有效,但语法差别很明显,但是当你处理VB.NET数组时,有一点需要考虑。
在VB.NET中,声明数组设置数组的最大有效索引值。它不是我们想要存储在数组中的元素的数量。
index
因此,这个循环将使用5个整数填充数组,而不会导致任何IndexOutOfRangeException
' declares an array with space for 5 integer
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer
此异常意味着您尝试使用无效索引按索引访问集合项。当索引低于集合的下限或大于时,索引无效 等于它包含的元素数量。 数组声明中定义的最大允许索引
关于Index out of bound of exception的简单解释是:
试想一下火车的舱室是D1,D2,D3。一名乘客来到火车,他有D4的机票。现在会发生什么。乘客想进入一个不存在的隔间,所以很明显会出现问题。
相同的场景:每当我们尝试访问数组列表等时,我们只能访问数组中的现有索引。 For i As Integer = 0 To 4
myArray(i) = i
Next
和array[0]
已经存在。如果我们尝试访问array[1]
,它实际上并不存在,那么将出现索引超出范围的异常。
为了便于理解问题,想象一下我们编写了这段代码:
array[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());
}
}
数组的大小为3(索引0,1和2),但for循环循环4次(0,1,2和3)。 因此,当它尝试使用(3)访问边界外时,它会抛出异常。
从很长的完全接受的答案的一方面来看,与许多其他异常类型相比,hello1
hello2
hello3
Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.
有一个重点,那就是:
通常存在复杂的程序状态,可能很难控制代码中的特定点,例如DB连接断开,因此无法检索输入的数据等......这种问题通常会导致某种类型的异常必须冒泡到更高的水平,因为它发生的地方在那时无法处理它。
IndexOutOfRangeException
通常是不同的,因为在大多数情况下,在提出异常时检查它是非常简单的。通常这种异常会被某些代码抛出,这些代码可以很容易地在它发生的地方处理问题 - 只需检查数组的实际长度即可。您不希望通过更高级处理此异常来“修复”此问题 - 而是确保它不会在第一个实例中抛出 - 在大多数情况下通过检查数组长度很容易。
另一种说法是,由于真正缺乏对输入或程序状态的控制,可能会出现其他异常但是IndexOutOfRangeException
往往只是导频(程序员)错误。