c# – 什么是IndexOutOfRangeException / ArgumentOutOfRangeException,我如何解决它?

我有一些代码,当它执行时,它抛出一个IndexOutOfRangeException,说,

Index was outside the bounds of the array.

这是什么意思,我能做些什么呢?

根据使用的类,它也可以是ArgumentOutOfRangeException

An exception of type ‘System.ArgumentOutOfRangeException’ occurred in mscorlib.dll but was not handled in user code Additional information: Index was out of range. Must be non-negative and less than the size of the collection.

它是什么?

此异常意味着您正在尝试使用无效的索引通过索引访问集合项。当索引低于集合的下限或大于或等于其包含的元素数量时,索引无效。

当它被抛出

给定一个数组声明为:

byte[] array = new byte[4];

您可以从0到3访问此数组,超出此范围的值将导致抛出IndexOutOfRangeException。在创建和访问数组时记住这一点。

数组长度
在C#中,通常,数组是基于0的。这意味着第一个元素具有索引0,最后一个元素具有索引长度-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;
    }
}

上限不包括
在下面的示例中,我们创建一个原始的二维数组。每个项目表示一个像素,索引从(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开始的,图像中的最后一个(右下)像素是像素[imageWidth – 1,imageHeight – 1]:

pixels[imageWidth, imageHeight] = Color.Black;

在另一种情况下,你可能会得到此代码的ArgumentOutOfRangeException(例如,如果你使用GetPixel方法在Bitmap类)。

数组不成长
数组很快。非常快的线性搜索相比,每一个其他集合。这是因为项目在内存中是连续的,因此可以计算内存地址(并且增量只是一个加法)。不需要遵循节点列表,简单的数学!你支付这个限制:它们不能增长,如果你需要更多的元素你需要重新分配该数组(如果旧项目必须复制到一个新的块,这可能是膨胀的)。您可以使用Array.Resize< T>()调整它们的大小,本示例向现有数组添加一个新条目:

Array.Resize(ref array, array.Length + 1);

不要忘记有效的索引从0到长度 – 1.如果你只是试图在长度分配一个项目,你会得到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(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;
}

当然,这是一个非常可怕的实现,但我想说明的是,它将失败的负数和超过最大数字。

如何适用于List<T>

与数组相同的情况 – 有效索引的范围 – 0(列表的索引始终从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和Columns
想象一下,您试图使用此代码从数据库读取数据:

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等)共享。

其他
在抛出此异常时,还有另一个(记录的)情况:if,在DataView中,提供给DataViewSort属性的数据列名无效。

如何避免

在这个例子中,为了简单起见,我假设数组总是单维的,基于0的。如果你想要严格(或者你的开发库),你可能需要替换0 GetLowerBound(0)和.Length与GetUpperBound(0)(当然如果你有参数类型System.Array,它不’ t应用于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,这个函数的用户可能期望这是因为他们传递一个数组,但它并不总是那么明显。我建议总是验证公共函数的参数:

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()替换逻辑:

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;

您发现您的错误,检查索引如何增加。这样对吗?检查数组是如何分配的,与索引如何增加一致?根据你的规格是正确的吗?如果你回答是所有这些问题,那么你会找到很好的帮助在这里StackOverflow,但请先自己检查。你会节省自己的时间!

一个好的起点是始终使用断言和验证输入。你甚至可能想使用代码合约。当出了问题,你不能弄清楚发生了什么,快速看看你的代码,那么你必须求助一个老朋友:调试器。只需在Visual Studio(或你最喜欢的IDE)中的调试中运行你的应用程序,你会看到究竟哪一行抛出这个异常,涉及哪个数组,以及你想要使用的索引。真的,99%的时候,你会在几分钟内自己解决它。

如果这种情况发生在生产中,那么你最好在断言的代码中添加断言,可能我们不会在代码中看到你自己看不到的东西(但你总是可以下注)。

http://stackoverflow.com/questions/20940979/what-is-an-indexoutofrangeexception-argumentoutofrangeexception-and-how-do-i-f

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:c# – 什么是IndexOutOfRangeException / ArgumentOutOfRangeException,我如何解决它?