什么是IndexOutOfRangeException/ArgumentOutOfRangeException以及如何解决?

 Android代码 发布于 2023-02-06 13:12

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

指数数组的边界之外.

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

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

mscorlib.dll中出现"System.ArgumentOutOfRangeException"类型的异常但未在用户代码中处理附加信息:索引超出范围.必须是非负数且小于集合的大小.

Adriano Repe.. 217

它是什么?

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

当它被抛出

给定一个数组声明为:

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此代码(例如,如果您在类GetPixel上使用方法Bitmap).

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

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

不要忘了有效的索引,从0Length - 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中引入了nullables,无论如何它也是多年来使用的众所周知的约定)如果它们没有找到任何东西 让我们假设你有一个与字符串相当的对象数组.您可能会想要编写此代码:

// 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 numbers) {
    int[] result = new int[maximum + 1]; // Includes 0

    foreach (int number in numbers)
        ++result[number];

    return result;
}

当然这是一个非常糟糕的实现,但我想要表明的是,它会因为上面的负数和数字而失败maximum.

它是如何适用的List

与数组相同的情况 - 有效索引的范围 - 0(List的索引总是从0开始)到list.Count- 访问此范围之外的元素将导致异常.

请注意,List抛出ArgumentOutOfRangeException数组使用的相同情况IndexOutOfRangeException.

与数组不同,List启动为空 - 因此尝试访问刚创建的列表的项目会导致此异常.

var list = new List();

常见的情况是使用索引填充列表(类似Dictionary)会导致异常:

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等等).

如果使用带有列名并传递无效列名的索引器运算符的IDataReader重载,也可以获得相同的异常.
例如,假设您已检索到名为Column1的列,但随后尝试使用该字段检索该字段的值

 var data = dr["Colum1"];  // Missing the n in Column1.

发生这种情况是因为实现了索引器运算符,试图检索不存在的Colum1字段的索引.当其内部帮助程序代码返回-1作为"Colum1"的索引时,GetOrdinal方法将抛出此异常.

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

如何避免

在这个例子中,让我假设,为简单起见,数组总是单维的和基于0的.如果你想成为严格的(或your're开发库),你可能需要更换0GetLowerBound(0).LengthGetUpperBound(0)(当然,如果你有参数类型为System.ArraY,它并不适用于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[] 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来自参数,则应始终验证它们(抛出适当的ArgumentExceptionArgumentOutOfRangeException).在下一个示例中,错误的参数可能会导致IndexOutOfRangeException,此函数的用户可能会期望这样,因为他们传递的是数组,但并不总是那么明显.我建议总是验证公共函数的参数:

static void SetRange(T[] array, int from, int length, Func 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关于调试小程序的博客文章,我不会在这里重复他的话,但绝对必须阅读.

你有源代码,你有带栈跟踪的异常消息.去那里,选择正确的行号,你会看到:

array[index] = newValue;

您发现了错误,请检查index增加的方式.这样对吗?检查数组是如何分配的,是如何index增加的?根据你的指定是否正确?如果您对所有这些问题的回答是肯定的,那么您将在StackOverflow上找到很好的帮助,但请首先自行检查.你会节省自己的时间!

一个好的起点是始终使用断言并验证输入.您甚至可能希望使用代码合同.当出现问题并且您无法弄清楚快速查看代码时会发生什么,那么您必须求助于一位老朋友:调试器.只需在Visual Studio(或您最喜欢的IDE)中的调试中运行您的应用程序,您就会看到确切的哪一行抛出此异常,涉及哪个数组以及您尝试使用哪个索引.真的,99%的时间你会在几分钟内自己解决它.

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

3 个回答
  • 关于Index out of bound of exception的简单解释是:

    试想一下火车的舱室是D1,D2,D3.一名乘客来到火车,他有D4的机票.现在会发生什么.乘客想进入一个不存在的隔间,所以很明显会出现问题.

    相同的场景:每当我们尝试访问数组列表等时,我们只能访问数组中的现有索引.array[0]并且array[1]存在.如果我们尝试访问array[3],实际上并不存在,那么将出现索引超出范围的异常.

    2023-02-06 13:15 回答
  • 为了便于理解问题,想象一下我们编写了这段代码:

    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)访问边界外时,它会抛出异常.

    2023-02-06 13:16 回答
  • 它是什么?

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

    当它被抛出

    给定一个数组声明为:

    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此代码(例如,如果您在类GetPixel上使用方法Bitmap).

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

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

    不要忘了有效的索引,从0Length - 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中引入了nullables,无论如何它也是多年来使用的众所周知的约定)如果它们没有找到任何东西 让我们假设你有一个与字符串相当的对象数组.您可能会想要编写此代码:

    // 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>抛出ArgumentOutOfRangeException数组使用的相同情况IndexOutOfRangeException.

    与数组不同,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等等).

    如果使用带有列名并传递无效列名的索引器运算符的IDataReader重载,也可以获得相同的异常.
    例如,假设您已检索到名为Column1的列,但随后尝试使用该字段检索该字段的值

     var data = dr["Colum1"];  // Missing the n in Column1.
    

    发生这种情况是因为实现了索引器运算符,试图检索不存在的Colum1字段的索引.当其内部帮助程序代码返回-1作为"Colum1"的索引时,GetOrdinal方法将抛出此异常.

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

    如何避免

    在这个例子中,让我假设,为简单起见,数组总是单维的和基于0的.如果你想成为严格的(或your're开发库),你可能需要更换0GetLowerBound(0).LengthGetUpperBound(0)(当然,如果你有参数类型为System.ArraY,它并不适用于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来自参数,则应始终验证它们(抛出适当的ArgumentExceptionArgumentOutOfRangeException).在下一个示例中,错误的参数可能会导致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关于调试小程序的博客文章,我不会在这里重复他的话,但绝对必须阅读.

    你有源代码,你有带栈跟踪的异常消息.去那里,选择正确的行号,你会看到:

    array[index] = newValue;
    

    您发现了错误,请检查index增加的方式.这样对吗?检查数组是如何分配的,是如何index增加的?根据你的指定是否正确?如果您对所有这些问题的回答是肯定的,那么您将在StackOverflow上找到很好的帮助,但请首先自行检查.你会节省自己的时间!

    一个好的起点是始终使用断言并验证输入.您甚至可能希望使用代码合同.当出现问题并且您无法弄清楚快速查看代码时会发生什么,那么您必须求助于一位老朋友:调试器.只需在Visual Studio(或您最喜欢的IDE)中的调试中运行您的应用程序,您就会看到确切的哪一行抛出此异常,涉及哪个数组以及您尝试使用哪个索引.真的,99%的时间你会在几分钟内自己解决它.

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

    2023-02-06 13:16 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有