什么是IndexOutOfRangeException / ArgumentOutOfRangeException,如何解决?
我有一些代码,当它执行时,它抛出一个IndexOutOfRangeException
,说,
指数数组的边界之外。
这是什么意思,我能做些什么呢?
根据所使用的类别,它也可以是ArgumentOutOfRangeException
mscorlib.dll中发生types“System.ArgumentOutOfRangeException”的exception,但未在用户代码中处理其他信息:索引超出范围。 必须是非负的,并且小于集合的大小。
它是什么?
这个exception意味着你试图通过索引访问一个集合项目,使用一个无效的索引。 当索引低于集合的下限或大于或等于其包含的元素数目时,该索引无效。
当它被投掷
给定一个数组声明为:
byte[] array = new byte[4];
您可以从0到3访问此数组,超出此范围的值将导致引发IndexOutOfRangeException
。 记住这一点,当你创build和访问一个数组。
数组长度
在C#中,通常,数组是基于0的。 这意味着第一个元素的索引为0,最后一个元素的索引为Length - 1
(其中Length
是数组中的项目总数),所以这段代码不起作用:
array[array.Length] = 0;
此外请注意,如果你有一个multidimensional array,那么你不能使用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; } }
上限不包括在内
在下面的例子中,我们创build了一个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
方法)。
数组不增长
一个数组很快。 相比于其他任何collections,线性search速度非常快。 这是因为项目在内存中是连续的,因此可以计算内存地址(并且增量只是一个加法)。 无需遵循节点列表,简单的math! 如果你需要更多的元素来重新分配这个数组(如果旧的项目必须被复制到一个新的块中,这可能是很大的)。 你用Array.Resize<T>()
调整它们的大小,这个例子给现有的数组添加一个新的条目:
Array.Resize(ref array, array.Length + 1);
不要忘记,有效的索引从0
到Length - 1
。 如果您只是尝试在Length
处指定项目,您将得到IndexOutOfRangeException
(如果您认为可能会使用类似于其他集合的Insert
方法的语法增加,则此行为可能会使您感到困惑)。
自定义下限的特殊数组
数组中的第一项始终为索引0 。 这并不总是如此,因为您可以创build一个具有自定义下限的数组:
var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });
在那个例子中,数组的索引从1到4是有效的。当然,上限是不能改变的。
错误的参数
如果您使用未经validation的参数(来自用户input或来自函数用户)访问数组,则可能会出现以下错误:
private static string[] RomanNumbers = new string[] { "I", "II", "III", "IV", "V" }; public static string Romanize(int number) { return RomanNumbers[number]; }
意外的结果
这个exception也可能是由于另外一个原因而引发的:按照惯例,如果没有find任何东西的话,许多search函数将会返回-1(在.NET 2.0中引入了可空string,无论如何它也是一个众所周知的惯例) 。 让我们想象一下,你有一个与string相当的对象数组。 你可能会想写这个代码:
// 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
没有项目满足search条件,这将失败,因为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
– 访问此范围之外的元素将导致exception。
请注意,对于数组使用IndexOutOfRangeException
情况, List<T>
将引发ArgumentOutOfRangeException
。
与数组不同, List<T>
开始为空 – 所以试图访问刚刚创build的列表的项目导致这个exception。
var list = new List<int>();
常见的情况是用索引来填充列表(类似于Dictionary<int, T>
)会导致exception:
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重载,并且传入一个无效的列名称,那么也可以得到相同的exception。
例如,假设您已经检索了一个名为Column1的列,但是您尝试用该列检索该字段的值
var data = dr["Colum1"]; // Missing the n in Column1.
发生这种情况是因为索引器操作符试图检索不存在的Colum1字段的索引。 GetOrdinal方法将在内部帮助程序代码返回-1作为“Colum1”的索引时抛出此exception。
其他
在引发此exception时有另一种(logging)的情况:如果在DataView
,提供给DataViewSort
属性的数据列名称无效。
如何避免
在这个例子中,为了简单起见,我假设数组始终是单维的,基于0的。 如果你想严格(或者你正在开发一个库),你可能需要用GetLowerBound(0)
和GetLowerBound(0)
replaceGetUpperBound(0)
(当然如果你有System.Arra
types的参数,它不会不适用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 })); }
validation参数
如果索引来自一个参数,你应该总是validation它们(抛出适当的ArgumentException
或ArgumentOutOfRangeException
)。 在下一个例子中,错误的参数可能会导致IndexOutOfRangeException
,这个函数的用户可能会期望这个,因为他们正在传递一个数组,但并不总是那么明显。 我build议总是validation公共职能的参数:
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); }
如果函数是私有的,你可以简单地用Debug.Assert()
replace逻辑:
Debug.Assert(from >= 0 && from < array.Length);
检查对象状态
数组索引可能不直接来自参数。 它可能是对象状态的一部分。 一般来说,validation对象状态(如果需要的话,本身和函数参数)总是一个很好的实践。 你可以使用Debug.Assert()
,抛出一个适当的exception(更详细地描述这个问题),或者像下面这样处理:
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]; } }
validation返回值
在之前的例子中,我们直接使用Array.IndexOf()
返回值。 如果我们知道它可能会失败,那么最好处理这种情况:
int index = myArray[Array.IndexOf(myArray, "Debug"); if (index != -1) { } else { }
如何debugging
在我看来,大部分关于这个错误的问题都可以简单的避免。 你花时间编写一个适当的问题(用一个小的工作例子和一个小的解释)可能比你需要更多的时间debugging你的代码。 首先阅读Eric Lippert关于小程序debugging的博客文章,我不会在这里重复他的话,但绝对是必读的 。
你有源代码,你有堆栈跟踪exception消息。 去那里,select正确的行号,你会看到:
array[index] = newValue;
你发现你的错误,检查index
如何增加。 这样对吗? 检查数组是如何分配的,与index
如何增加是一致的? 根据你的指定是否正确? 如果您对所有这些问题都回答“ 是” ,那么在StackOverflow中您将find很好的帮助,但请首先自行检查。 你会节省你自己的时间!
一个好的起点是总是使用断言和validationinput。 你甚至可能想要使用代码合同。 当出现问题,你不能找出快速查看你的代码会发生什么,那么你必须诉诸老朋友: debugging器 。 只要在Visual Studio(或者你最喜欢的IDE)中debugging你的应用程序,就可以看到哪一行引发了这个exception,涉及到哪个数组以及你试图使用哪个索引。 真的,99%的时间你会在几分钟内自己解决。
如果在生产中发生这种情况,那么你最好在有罪的代码中添加断言,可能我们不会在你的代码中看到你自己看不到的东西(但你总是可以打赌)。
关于索引超出限制的exception的简单解释是:
试想一列车是D1,D2,D3。 一名乘客进入火车,他有D4票。 现在会发生什么。 乘客想要进入一个不存在的车厢,所以显然会出现问题。
同样的情况:每当我们尝试访问数组列表等,我们只能访问数组中的现有索引。 array[0]
和array[1]
是存在的。 如果我们尝试访问array[3]
,实际上并不存在,所以会出现索引超出范围的exception。