Apa itu IndexOutOfRangeException / ArgumentOutOfRangeException dan bagaimana cara memperbaikinya?

191

Saya memiliki beberapa kode dan ketika dijalankan, ia melempar IndexOutOfRangeException, mengatakan,

Indeks melewati batas susunan.

Apa artinya ini, dan apa yang bisa saya lakukan?

Tergantung pada kelas yang digunakan, bisa juga ArgumentOutOfRangeException

Pengecualian dari tipe 'System.ArgumentOutOfRangeException' terjadi di mscorlib.dll tetapi tidak ditangani dalam kode pengguna Informasi tambahan: Indeks berada di luar jangkauan. Harus non-negatif dan kurang dari ukuran koleksi.

Adriano Repetti
sumber
Dalam koleksi Anda jika Anda hanya memiliki 4 item, tetapi kode mencoba untuk mendapatkan item dalam indeks 5. Ini akan membuang IndexOutOfRangeException. Periksa indeks = 5; if (items.Length> = index) Console.WriteLine (intems [index]);
Babu Kumarasamy

Jawaban:

232

Apa itu?

Pengecualian ini berarti Anda mencoba mengakses item koleksi berdasarkan indeks, menggunakan indeks yang tidak valid. Indeks tidak valid jika lebih rendah dari batas bawah koleksi atau lebih besar dari atau sama dengan jumlah elemen yang dikandungnya.

Saat Dilempar

Diberikan array yang dinyatakan sebagai:

byte[] array = new byte[4];

Anda dapat mengakses array ini dari 0 hingga 3, nilai di luar rentang ini akan menyebabkan IndexOutOfRangeExceptiondilemparkan. Ingat ini ketika Anda membuat dan mengakses array.

Panjang Array
Dalam C #, biasanya, array berbasis 0. Ini berarti bahwa elemen pertama memiliki indeks 0 dan elemen terakhir memiliki indeks Length - 1(di mana Lengthjumlah total item dalam array) sehingga kode ini tidak berfungsi:

array[array.Length] = 0;

Selain itu harap dicatat bahwa jika Anda memiliki array multidimensi maka Anda tidak dapat menggunakan Array.Lengthuntuk kedua dimensi, Anda harus menggunakan 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;
    }
}

Batas Atas Tidak Termasuk
Dalam contoh berikut ini kami membuat array dua dimensi mentah Color. Setiap item mewakili pixel, indeks adalah dari (0, 0)ke (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;
    }
}

Kode ini kemudian akan gagal karena array berbasis 0 dan piksel terakhir (kanan bawah) pada gambar adalah pixels[imageWidth - 1, imageHeight - 1]:

pixels[imageWidth, imageHeight] = Color.Black;

Dalam skenario lain Anda mungkin mendapatkan ArgumentOutOfRangeExceptionkode ini (misalnya jika Anda menggunakan GetPixelmetode di Bitmapkelas).

Array Jangan Tumbuh
Array cepat. Sangat cepat dalam pencarian linear dibandingkan dengan setiap koleksi lainnya. Itu karena item berdekatan dalam memori sehingga alamat memori dapat dihitung (dan penambahan hanyalah tambahan). Tidak perlu mengikuti daftar simpul, matematika sederhana! Anda membayar ini dengan batasan: mereka tidak dapat tumbuh, jika Anda membutuhkan lebih banyak elemen, Anda perlu realokasi array itu (ini mungkin memakan waktu yang relatif lama jika item lama harus disalin ke blok baru). Anda mengubah ukurannya dengan Array.Resize<T>(), contoh ini menambahkan entri baru ke array yang ada:

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

Jangan lupa bahwa indeks yang valid dari 0ke Length - 1. Jika Anda hanya mencoba menetapkan item pada LengthAnda akan mendapatkan IndexOutOfRangeException(perilaku ini dapat membingungkan Anda jika Anda berpikir mereka dapat meningkat dengan sintaksis yang mirip dengan Insertmetode koleksi lain).

Array Khusus Dengan Kustom Batas Bawah
Item pertama dalam array selalu indeks 0 . Ini tidak selalu benar karena Anda dapat membuat array dengan batas bawah khusus:

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

Dalam contoh itu, indeks array valid dari 1 hingga 4. Tentu saja, batas atas tidak dapat diubah.

Argumen Salah
Jika Anda mengakses array menggunakan argumen yang tidak divalidasi (dari input pengguna atau dari pengguna fungsi) Anda mungkin mendapatkan kesalahan ini:

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

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

Hasil yang Tidak Terduga
Pengecualian ini mungkin dilemparkan karena alasan lain juga: berdasarkan konvensi, banyak fungsi pencarian akan mengembalikan -1 (nullables telah diperkenalkan dengan .NET 2.0 dan lagi pula itu juga merupakan konvensi terkenal yang digunakan selama bertahun-tahun) jika mereka tidak melakukannya. tidak menemukan apa pun. Mari kita bayangkan Anda memiliki array objek yang sebanding dengan string Anda mungkin berpikir untuk menulis kode ini:

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

Ini akan gagal jika tidak ada item dalam myArray akan memenuhi kondisi pencarian karena Array.IndexOf()akan mengembalikan -1 dan kemudian akses array akan melempar.

Contoh berikutnya adalah contoh naif untuk menghitung kemunculan himpunan angka tertentu (mengetahui angka maksimum dan mengembalikan array di mana item pada indeks 0 mewakili angka 0, item pada indeks 1 mewakili angka 1 dan seterusnya):

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

Tentu saja, ini adalah implementasi yang sangat mengerikan tetapi yang ingin saya tunjukkan adalah bahwa itu akan gagal untuk angka negatif dan angka di atas maximum .

Bagaimana ini berlaku untuk List<T> ?

Kasing yang sama dengan array - rentang indeks yang valid - 0 (List indeks selalu dimulai dengan 0) untuk list.Countmengakses elemen di luar rentang ini akan menyebabkan pengecualian.

Perhatikan bahwa List<T>lemparanArgumentOutOfRangeException untuk kasus yang sama di mana array digunakan IndexOutOfRangeException.

Tidak seperti array, List<T>mulai kosong - jadi mencoba mengakses item dari daftar yang baru saja dibuat mengarah ke pengecualian ini.

var list = new List<int>();

Kasus umum adalah mengisi daftar dengan pengindeksan (mirip dengan Dictionary<int, T>) akan menyebabkan pengecualian:

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

IDataReader dan Kolom
Bayangkan Anda mencoba membaca data dari database dengan kode ini:

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() akan melempar IndexOutOfRangeException karena dataset Anda hanya memiliki dua kolom tetapi Anda mencoba untuk mendapatkan nilai dari yang ketiga (indeks selalu berbasis 0).

Harap dicatat bahwa perilaku ini dibagikan dengan sebagian besar IDataReaderimplementasi (SqlDataReader ,OleDbDataReader dan sebagainya).

Anda bisa mendapatkan pengecualian yang sama juga jika Anda menggunakan IDataReader overload dari operator pengindeks yang mengambil nama kolom dan meneruskan nama kolom yang tidak valid.
Misalkan misalnya Anda telah mengambil kolom bernama Column1 tetapi kemudian Anda mencoba mengambil nilai bidang itu dengan

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

Ini terjadi karena operator pengindeks dilaksanakan mencoba mengambil indeks dari Colum1 bidang yang tidak ada. Metode GetOrdinal akan melempar pengecualian ini ketika kode pembantu internal mengembalikan -1 sebagai indeks "Colum1".

Lainnya
Ada kasus lain (terdokumentasi) ketika pengecualian ini dilemparkan: jika, dalam DataView, nama kolom data yang dipasok ke DataViewSortproperti tidak valid.

Cara Menghindari

Dalam contoh ini, izinkan saya berasumsi, untuk kesederhanaan, bahwa array selalu berbasis monodimensi dan 0. Jika Anda ingin ketat (atau Anda sedang mengembangkan perpustakaan), Anda mungkin perlu mengganti 0dengan GetLowerBound(0)dan .Lengthdengan GetUpperBound(0)(tentu saja jika Anda memiliki parameter tipe System.Array, itu tidak berlaku untuk T[]). Harap perhatikan bahwa dalam hal ini, batas atas inklusif maka kode ini:

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

Harus ditulis ulang seperti ini:

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

Harap perhatikan bahwa ini tidak diperbolehkan (itu akan melempar InvalidCastException), itu sebabnya jika parameter T[]Anda aman tentang array batas bawah khusus:

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

Validasikan Parameter
Jika indeks berasal dari parameter, Anda harus selalu memvalidasinya (melempar sesuai ArgumentExceptionatau ArgumentOutOfRangeException). Dalam contoh berikut, parameter yang salah dapat menyebabkan IndexOutOfRangeException, pengguna fungsi ini dapat mengharapkan ini karena mereka melewati array tetapi tidak selalu begitu jelas. Saya sarankan untuk selalu memvalidasi parameter untuk fungsi publik:

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

Jika fungsi bersifat pribadi, Anda cukup mengganti iflogika dengan Debug.Assert():

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

Periksa Object State
Array index mungkin tidak datang langsung dari parameter. Ini mungkin bagian dari status objek. Secara umum selalu merupakan praktik yang baik untuk memvalidasi keadaan objek (dengan sendirinya dan dengan parameter fungsi, jika diperlukan). Anda dapat menggunakan Debug.Assert(), melempar pengecualian yang tepat (lebih deskriptif tentang masalah) atau mengatasinya seperti dalam contoh ini:

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

Validasi Nilai Pengembalian
Dalam salah satu contoh sebelumnya, kami secara langsung menggunakan Array.IndexOf()nilai balik. Jika kita tahu itu mungkin gagal maka lebih baik untuk menangani kasus itu:

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

Bagaimana cara Debug

Menurut pendapat saya, sebagian besar pertanyaan, di sini di SO, tentang kesalahan ini bisa dihindari. Waktu yang Anda habiskan untuk menulis pertanyaan yang tepat (dengan contoh kerja kecil dan sedikit penjelasan) dapat dengan mudah lebih dari waktu yang Anda perlukan untuk men-debug kode Anda. Pertama-tama, baca posting blog Eric Lippert ini tentang debugging program kecil , saya tidak akan mengulangi kata-katanya di sini tapi itu benar-benar harus dibaca .

Anda memiliki kode sumber, Anda memiliki pesan pengecualian dengan jejak tumpukan. Pergi ke sana, pilih nomor baris kanan dan Anda akan melihat:

array[index] = newValue;

Anda menemukan kesalahan Anda, periksa bagaimana indexpeningkatannya. Apakah tepat? Periksa bagaimana array dialokasikan, apakah koheren dengan indexpeningkatan? Apakah benar sesuai dengan spesifikasi Anda? Jika Anda menjawab ya untuk semua pertanyaan ini, maka Anda akan menemukan bantuan yang baik di sini di StackOverflow tapi tolong periksa dulu sendiri. Anda akan menghemat waktu Anda sendiri!

Titik awal yang baik adalah untuk selalu menggunakan pernyataan dan untuk memvalidasi input. Anda bahkan mungkin ingin menggunakan kontrak kode. Ketika terjadi kesalahan dan Anda tidak dapat mengetahui apa yang terjadi dengan melihat kode Anda dengan cepat maka Anda harus beralih ke teman lama: debugger . Cukup jalankan aplikasi Anda dalam debug di dalam Visual Studio (atau IDE favorit Anda), Anda akan melihat baris mana yang melempar pengecualian ini, array mana yang terlibat dan indeks mana yang Anda coba gunakan. Sungguh, 99% dari waktu Anda akan menyelesaikannya sendiri dalam beberapa menit.

Jika ini terjadi dalam produksi maka Anda sebaiknya menambahkan pernyataan dalam kode yang memberatkan, mungkin kami tidak akan melihat dalam kode Anda apa yang tidak dapat Anda lihat sendiri (tetapi Anda selalu bisa bertaruh).

Sisi VB.NET dari cerita ini

Segala sesuatu yang telah kami katakan dalam jawaban C # valid untuk VB.NET dengan perbedaan sintaks yang jelas tetapi ada poin penting untuk dipertimbangkan ketika Anda berurusan dengan array VB.NET.

Di VB.NET, array dinyatakan menetapkan nilai indeks valid maksimum untuk array. Bukan hitungan elemen yang ingin kita simpan dalam array.

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

Jadi loop ini akan mengisi array dengan 5 integer tanpa menyebabkan IndexOutOfRangeException

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

Aturan VB.NET

Pengecualian ini berarti Anda mencoba mengakses item koleksi berdasarkan indeks, menggunakan indeks yang tidak valid. Indeks tidak valid jika lebih rendah dari batas bawah koleksi atau lebih besar darisama dengan jumlah elemen yang dikandungnya. indeks maksimum yang diizinkan didefinisikan dalam deklarasi array

Adriano Repetti
sumber
19

Penjelasan sederhana tentang apa Indeks keluar dari batas adalah:

Bayangkan satu kereta ada kompartemennya D1, D2, D3. Satu penumpang datang untuk memasuki kereta dan dia memiliki tiket untuk D4. sekarang apa yang akan terjadi penumpang ingin memasuki kompartemen yang tidak ada sehingga jelas masalah akan muncul.

Skenario yang sama: setiap kali kita mencoba mengakses daftar array, dll. Kita hanya dapat mengakses indeks yang ada dalam array. array[0]dan array[1]ada. Jika kami mencoba mengakses array[3], sebenarnya tidak ada di sana, jadi indeks yang keluar dari pengecualian akan muncul.

Lijo
sumber
10

Untuk memahami masalahnya dengan mudah, bayangkan kami menulis kode ini:

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

Hasilnya adalah:

hello1
hello2
hello3

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

Ukuran array adalah 3 (indeks 0, 1 dan 2), tetapi loop for-loop 4 kali (0, 1, 2 dan 3).
Jadi ketika mencoba mengakses di luar batas dengan (3) ia melempar pengecualian.

Snr
sumber
1

Sisi dari jawaban yang diterima sangat lama ada poin penting untuk IndexOutOfRangeExceptiondibandingkan dengan banyak jenis pengecualian lainnya, dan itu adalah:

Seringkali ada keadaan program yang kompleks yang mungkin sulit untuk memiliki kontrol pada titik tertentu dalam kode misalnya koneksi DB turun sehingga data untuk input tidak dapat diambil dll ... Masalah seperti ini sering mengakibatkan Pengecualian dari beberapa jenis yang harus menggelembung ke tingkat yang lebih tinggi karena ketika itu terjadi tidak ada cara untuk menghadapinya pada saat itu.

IndexOutOfRangeExceptionumumnya berbeda karena dalam banyak kasus itu cukup sepele untuk memeriksa pada titik di mana pengecualian sedang diajukan. Umumnya pengecualian semacam ini dilemparkan oleh beberapa kode yang dapat dengan mudah menangani masalah di tempat itu terjadi - hanya dengan memeriksa panjang aktual array. Anda tidak ingin 'memperbaiki' ini dengan menangani pengecualian ini lebih tinggi - tetapi dengan memastikannya tidak dilemparkan pada contoh pertama - yang dalam banyak kasus mudah dilakukan dengan memeriksa panjang array.

Cara lain untuk menempatkan ini adalah bahwa pengecualian lain dapat muncul karena kurangnya kontrol atas input atau status program TETAPI IndexOutOfRangeExceptionlebih sering daripada tidak hanya kesalahan pilot (programmer).

Ricibob
sumber