IndexOutOfRangeException / ArgumentOutOfRangeException là gì và làm cách nào để khắc phục nó?


190

Tôi có một số mã và khi nó thực thi, nó ném một IndexOutOfRangeException, nói,

Index nằm ngoài giới hạn của mảng.

Điều này có nghĩa là gì, và tôi có thể làm gì về nó?

Tùy thuộc vào các lớp được sử dụng, nó cũng có thể được ArgumentOutOfRangeException

Một ngoại lệ của loại 'System.ArgumentOutOfRangeException' đã xảy ra trong mscorlib.dll nhưng không được xử lý trong mã người dùng Thông tin bổ sung: Chỉ mục nằm ngoài phạm vi. Phải không âm và nhỏ hơn kích thước của bộ sưu tập.


Trong bộ sưu tập của bạn nếu bạn chỉ có 4 mục, nhưng mã đã cố lấy một mục trong chỉ mục 5. Điều này sẽ ném IndexOutOfRangeException. Kiểm tra chỉ số = 5; if (items.Ldrops> = index) Console.WriteLine (intems [index]);
Babu Kumaraamy

Câu trả lời:


230

Nó là gì?

Ngoại lệ này có nghĩa là bạn đang cố gắng truy cập một mục bộ sưu tập theo chỉ mục, sử dụng chỉ mục không hợp lệ. Một chỉ mục không hợp lệ khi nó thấp hơn giới hạn dưới của bộ sưu tập hoặc lớn hơn hoặc bằng số lượng phần tử mà nó chứa.

Khi nó được ném

Cho một mảng được khai báo là:

byte[] array = new byte[4];

Bạn có thể truy cập mảng này từ 0 đến 3, các giá trị ngoài phạm vi này sẽ IndexOutOfRangeExceptionbị ném. Hãy nhớ điều này khi bạn tạo và truy cập vào một mảng.

Độ dài mảng
Trong C #, thông thường, các mảng dựa trên 0. Điều đó có nghĩa là phần tử đầu tiên có chỉ số 0 và phần tử cuối cùng có chỉ mục Length - 1(trong đó Lengthtổng số mục trong mảng) nên mã này không hoạt động:

array[array.Length] = 0;

Ngoài ra, xin lưu ý rằng nếu bạn có một mảng nhiều chiều thì bạn không thể sử dụng Array.Lengthcho cả hai chiều, bạn phải sử dụng 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;
    }
}

Giới hạn trên không được bao gồm
Trong ví dụ sau, chúng tôi tạo ra một mảng hai chiều thô của Color. Mỗi mục đại diện cho một pixel, các chỉ số là từ (0, 0)đến (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;
    }
}

Mã này sau đó sẽ thất bại vì mảng dựa trên 0 và pixel cuối cùng (dưới cùng bên phải) trong hình ảnh là pixels[imageWidth - 1, imageHeight - 1] :

pixels[imageWidth, imageHeight] = Color.Black;

Trong một kịch bản khác, bạn có thể nhận được ArgumentOutOfRangeException mã này (ví dụ: nếu bạn đang sử dụng GetPixelphương thức trên một Bitmaplớp).

Mảng không tăng
Một mảng là nhanh. Rất nhanh trong tìm kiếm tuyến tính so với mọi bộ sưu tập khác. Đó là do các mục nằm liền kề trong bộ nhớ nên có thể tính địa chỉ bộ nhớ (và gia tăng chỉ là một bổ sung). Không cần phải theo một danh sách nút, toán đơn giản! Bạn trả tiền này với một giới hạn: chúng không thể phát triển, nếu bạn cần nhiều yếu tố hơn, bạn cần phân bổ lại mảng đó (điều này có thể mất một thời gian tương đối dài nếu các mục cũ phải được sao chép sang một khối mới). Bạn thay đổi kích thước chúng vớiArray.Resize<T>() , ví dụ này thêm một mục mới vào một mảng hiện có:

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

Đừng quên rằng các chỉ số hợp lệ là từ 0đến Length - 1. Nếu bạn chỉ đơn giản là cố gắng chỉ định một mục tại Lengthbạn sẽ nhận đượcIndexOutOfRangeException (hành vi này có thể làm bạn bối rối nếu bạn nghĩ rằng chúng có thể tăng theo cú pháp tương tự như Insertphương pháp của các bộ sưu tập khác).

Mảng đặc biệt với giới hạn dưới tùy chỉnh
Mục đầu tiên trong mảng luôn có chỉ số 0 . Điều này không phải lúc nào cũng đúng bởi vì bạn có thể tạo một mảng với giới hạn dưới tùy chỉnh:

var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });

Trong ví dụ đó, các chỉ số mảng có giá trị từ 1 đến 4. Tất nhiên, giới hạn trên không thể thay đổi.

Đối số sai
Nếu bạn truy cập vào một mảng bằng các đối số không được xác thực (từ đầu vào của người dùng hoặc từ người dùng chức năng), bạn có thể gặp lỗi này:

private static string[] RomanNumbers =
    new string[] { "I", "II", "III", "IV", "V" };

public static string Romanize(int number)
{
    return RomanNumbers[number];
}

Kết quả không mong đợi
Ngoại lệ này cũng có thể bị ném vì một lý do khác: theo quy ước, nhiều hàm tìm kiếm sẽ trả về -1 (nullables đã được giới thiệu với .NET 2.0 và dù sao đó cũng là một quy ước nổi tiếng được sử dụng từ nhiều năm) nếu chúng không ' t tìm thấy bất cứ điều gì Hãy tưởng tượng bạn có một mảng các đối tượng có thể so sánh với một chuỗi. Bạn có thể nghĩ để viết mã này:

// 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")]);

Điều này sẽ thất bại nếu không có mục nào myArraythỏa mãn điều kiện tìm kiếm vì Array.IndexOf()sẽ trả về -1 và sau đó truy cập mảng sẽ ném.

Ví dụ tiếp theo là một ví dụ ngây thơ để tính toán số lần xuất hiện của một tập hợp số đã cho (biết số tối đa và trả về một mảng trong đó mục tại chỉ số 0 đại diện cho số 0, các mục tại chỉ mục 1 đại diện cho số 1, v.v.):

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;
}

Tất nhiên, đó là một triển khai khá khủng khiếp nhưng điều tôi muốn thể hiện là nó sẽ thất bại đối với các số và số âm ở trên maximum .

Làm thế nào nó áp dụng cho List<T> ?

Các trường hợp tương tự như mảng - phạm vi chỉ mục hợp lệ - 0 ( Listchỉ mục của luôn bắt đầu bằng 0) đếnlist.Count - các phần tử truy cập bên ngoài phạm vi này sẽ gây ra ngoại lệ.

Lưu ý rằng List<T>ném ArgumentOutOfRangeExceptioncho các trường hợp tương tự trong đó sử dụng mảngIndexOutOfRangeException .

Không giống như mảng, List<T>bắt đầu trống - vì vậy cố gắng truy cập các mục của danh sách vừa tạo dẫn đến ngoại lệ này.

var list = new List<int>();

Trường hợp phổ biến là điền danh sách với lập chỉ mục (tương tự Dictionary<int, T>) sẽ gây ra ngoại lệ:

list[0] = 42; // exception
list.Add(42); // correct

IDataReader và Cột
Hãy tưởng tượng bạn đang cố đọc dữ liệu từ cơ sở dữ liệu với mã này:

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()sẽ ném IndexOutOfRangeExceptionvì tập dữ liệu của bạn chỉ có hai cột nhưng bạn đang cố lấy giá trị từ cột thứ 3 (chỉ số là luôn dựa trên 0).

Xin lưu ý rằng hành vi này được chia sẻ với hầu hết các IDataReadertriển khai ( SqlDataReader,OleDbDataReader và vân vân).

Bạn cũng có thể có ngoại lệ tương tự nếu bạn sử dụng quá tải IDataReader của toán tử lập chỉ mục lấy tên cột và chuyển tên cột không hợp lệ.
Ví dụ, giả sử bạn đã truy xuất một cột có tên là Cột1 nhưng sau đó bạn cố gắng truy xuất giá trị của trường đó bằng

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

Điều này xảy ra bởi vì toán tử chỉ mục được triển khai khi cố gắng truy xuất chỉ mục của trường Colum1 không tồn tại. Phương thức GetOrdinal sẽ đưa ra ngoại lệ này khi mã trình trợ giúp nội bộ của nó trả về -1 là chỉ số của "Colum1".

Các
trường hợp khác Có một trường hợp (được ghi lại) khi ngoại lệ này được ném: nếu, trong DataView, tên cột dữ liệu được cung cấp cho thuộc DataViewSorttính không hợp lệ.

Làm sao để tránh

Trong ví dụ này, để tôi giả sử, để đơn giản, các mảng luôn luôn là đơn hướng và dựa trên 0. Nếu bạn muốn nghiêm ngặt (hoặc bạn đang phát triển một thư viện), bạn có thể cần phải thay thế 0bằng GetLowerBound(0).Lengthbằng GetUpperBound(0)(tất nhiên nếu bạn có các tham số của loại System.Array, thì nó không áp dụng cho T[]). Xin lưu ý rằng trong trường hợp này, giới hạn trên được bao gồm thì mã này:

for (int i=0; i < array.Length; ++i) { }

Nên viết lại như thế này:

for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }

Xin lưu ý rằng điều này không được phép (nó sẽ ném InvalidCastException), đó là lý do tại sao nếu các tham số của T[]bạn an toàn về các mảng ràng buộc thấp hơn tùy chỉnh:

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 }));
}

Xác thực tham số
Nếu chỉ mục xuất phát từ một tham số, bạn phải luôn xác thực chúng (ném phù hợp ArgumentExceptionhoặc ArgumentOutOfRangeException). Trong ví dụ tiếp theo, các tham số sai có thể gây ra IndexOutOfRangeException, người dùng chức năng này có thể mong đợi điều này bởi vì họ đang truyền một mảng nhưng không phải lúc nào cũng quá rõ ràng. Tôi đề nghị luôn xác thực các tham số cho các chức năng công cộng:

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);
}

Nếu chức năng là riêng tư, bạn có thể chỉ cần thay thế iflogic bằng Debug.Assert():

Debug.Assert(from >= 0 && from < array.Length);

Kiểm tra
chỉ mục Mảng trạng thái có thể không đến trực tiếp từ một tham số. Nó có thể là một phần của trạng thái đối tượng. Nói chung luôn luôn là một thực tiễn tốt để xác nhận trạng thái đối tượng (bằng chính nó và với các tham số chức năng, nếu cần). Bạn có thể sử dụng Debug.Assert(), đưa ra một ngoại lệ thích hợp (mô tả thêm về vấn đề) hoặc xử lý như trong ví dụ này:

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];
        }
}

Xác thực giá trị trả về
Trong một trong các ví dụ trước, chúng tôi trực tiếp sử dụng Array.IndexOf()giá trị trả về. Nếu chúng ta biết nó có thể thất bại thì tốt hơn là xử lý trường hợp đó:

int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }

Cách gỡ lỗi

Theo tôi, hầu hết các câu hỏi, ở đây trên SO, về lỗi này có thể tránh được. Thời gian bạn dành để viết một câu hỏi thích hợp (với một ví dụ làm việc nhỏ và một lời giải thích nhỏ) có thể dễ dàng hơn nhiều so với thời gian bạn sẽ cần gỡ lỗi mã của mình. Trước hết, hãy đọc bài đăng trên blog của Eric Lippert về việc gỡ lỗi các chương trình nhỏ , tôi sẽ không lặp lại những lời của anh ấy ở đây nhưng nó hoàn toàn phải đọc .

Bạn có mã nguồn, bạn có thông báo ngoại lệ với dấu vết ngăn xếp. Đến đó, chọn đúng số dòng và bạn sẽ thấy:

array[index] = newValue;

Bạn đã tìm thấy lỗi của bạn, kiểm tra làm thế nào indextăng. Đúng không? Kiểm tra mảng được phân bổ như thế nào, có phù hợp với mức indextăng không? Có đúng theo thông số kỹ thuật của bạn? Nếu bạn trả lời cho tất cả những câu hỏi này, thì bạn sẽ tìm thấy sự trợ giúp tốt ở đây trên StackOverflow nhưng trước tiên hãy tự mình kiểm tra điều đó. Bạn sẽ tiết kiệm thời gian của riêng bạn!

Một điểm khởi đầu tốt là luôn luôn sử dụng các xác nhận và xác nhận đầu vào. Bạn thậm chí có thể muốn sử dụng hợp đồng mã. Khi có sự cố xảy ra và bạn không thể hiểu điều gì xảy ra với một cái nhìn nhanh về mã của mình thì bạn phải nhờ đến một người bạn cũ: trình gỡ lỗi . Chỉ cần chạy ứng dụng của bạn trong gỡ lỗi bên trong Visual Studio (hoặc IDE yêu thích của bạn), bạn sẽ thấy chính xác dòng nào ném ngoại lệ này, mảng nào có liên quan và chỉ mục nào bạn đang cố gắng sử dụng. Thực sự, 99% số lần bạn sẽ tự giải quyết trong vài phút.

Nếu điều này xảy ra trong sản xuất thì tốt hơn bạn nên thêm các xác nhận vào mã bị buộc tội, có lẽ chúng tôi sẽ không nhìn thấy trong mã của bạn những gì bạn không thể tự nhìn thấy (nhưng bạn luôn có thể đặt cược).

Phía VB.NET của câu chuyện

Tất cả những gì chúng tôi đã nói trong câu trả lời C # đều hợp lệ đối với VB.NET với sự khác biệt về cú pháp rõ ràng nhưng có một điểm quan trọng cần xem xét khi bạn xử lý các mảng VB.NET.

Trong VB.NET, các mảng được khai báo thiết lập giá trị chỉ mục hợp lệ tối đa cho mảng. Nó không phải là số lượng các yếu tố mà chúng tôi muốn lưu trữ trong mảng.

' declares an array with space for 5 integer 
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer

Vì vậy, vòng lặp này sẽ lấp đầy mảng với 5 số nguyên mà không gây ra bất kỳ IndexOutOfRangeException

For i As Integer = 0 To 4
    myArray(i) = i
Next

Quy tắc VB.NET

Ngoại lệ này có nghĩa là bạn đang cố gắng truy cập một mục bộ sưu tập theo chỉ mục, sử dụng chỉ mục không hợp lệ. Một chỉ mục không hợp lệ khi nó thấp hơn giới hạn dưới của bộ sưu tập hoặc lớn hơnbằng số lượng phần tử mà nó chứa. chỉ số tối đa được phép xác định trong khai báo mảng


19

Giải thích đơn giản về chỉ số ngoại lệ bị ràng buộc là:

Chỉ cần nghĩ rằng một chuyến tàu là có các khoang của nó là D1, D2, D3. Một hành khách đã đến để vào tàu và anh ta có vé cho D4. bây giờ chuyện gì sẽ xảy ra hành khách muốn vào một khoang không tồn tại nên rõ ràng vấn đề sẽ phát sinh.

Kịch bản tương tự: bất cứ khi nào chúng tôi cố gắng truy cập vào một danh sách mảng, v.v. chúng tôi chỉ có thể truy cập các chỉ mục hiện có trong mảng. array[0]array[1]đang tồn tại. Nếu chúng ta cố gắng truy cập array[3], nó thực sự không có ở đó, do đó, một chỉ số nằm ngoài ngoại lệ bị ràng buộc sẽ xuất hiện.


10

Để dễ hiểu vấn đề, hãy tưởng tượng chúng tôi đã viết mã này:

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());
    }
}

Kết quả sẽ là:

hello1
hello2
hello3

Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.

Kích thước của mảng là 3 (chỉ số 0, 1 và 2), nhưng các vòng lặp for 4 vòng (0, 1, 2 và 3).
Vì vậy, khi nó cố gắng truy cập bên ngoài giới hạn với (3), nó sẽ ném ngoại lệ.


1

Một khía cạnh từ câu trả lời được chấp nhận hoàn chỉnh rất dài có một điểm quan trọng cần IndexOutOfRangeExceptionso sánh với nhiều loại ngoại lệ khác, và đó là:

Thường có trạng thái chương trình phức tạp có thể khó kiểm soát tại một điểm cụ thể trong mã, ví dụ như kết nối DB bị hỏng để dữ liệu cho đầu vào không thể được truy xuất, v.v ... Loại vấn đề này thường dẫn đến Ngoại lệ của một loại nào đó phải bong bóng lên một mức cao hơn bởi vì nơi nó xảy ra không có cách nào để đối phó với nó tại thời điểm đó.

IndexOutOfRangeExceptionnói chung là khác nhau ở chỗ trong hầu hết các trường hợp, việc kiểm tra tại thời điểm mà ngoại lệ đang được nêu ra là khá nhỏ. Nói chung, loại ngoại lệ này bị ném bởi một số mã có thể rất dễ xử lý vấn đề tại nơi nó đang xảy ra - chỉ bằng cách kiểm tra độ dài thực tế của mảng. Bạn không muốn 'khắc phục' điều này bằng cách xử lý ngoại lệ này cao hơn - nhưng thay vào đó bằng cách đảm bảo nó không bị ném trong trường hợp đầu tiên - điều mà trong hầu hết các trường hợp rất dễ thực hiện bằng cách kiểm tra độ dài mảng.

Một cách khác để nói điều này là các ngoại lệ khác có thể phát sinh do thiếu kiểm soát thực sự đối với đầu vào hoặc trạng thái chương trình NHƯNG IndexOutOfRangeExceptionthường xuyên hơn không chỉ đơn giản là lỗi phi công (lập trình viên).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.