Apa itu IndexOutOfRangeException / ArgumentOutOfRangeException dan bagaimana cara memperbaikinya?
Saya memiliki beberapa kode dan ketika dijalankan, itu melontarkan IndexOutOfRangeException
, berkata,
Indeks melewati batas susunan.
Apa artinya ini, dan apa yang dapat saya lakukan?
Tergantung pada kelas yang digunakan, itu juga bisa ArgumentOutOfRangeException
Pengecualian jenis 'System.ArgumentOutOfRangeException' terjadi di mscorlib.dll tetapi tidak ditangani dalam kode pengguna Informasi tambahan: Indeks berada di luar jangkauan. Harus non-negatif dan lebih kecil dari ukuran koleksi.
Jawaban
Apa itu?
Pengecualian ini berarti Anda mencoba mengakses item koleksi menurut 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 di dalamnya.
Saat Itu Dilempar
Diberikan sebuah array yang dideklarasikan sebagai:
byte[] array = new byte[4];
Anda dapat mengakses larik ini dari 0 hingga 3, nilai di luar rentang ini akan IndexOutOfRangeException
dilemparkan. Ingat ini saat Anda membuat dan mengakses array.
Panjang Array
Dalam C #, biasanya, array berbasis 0. Artinya elemen pertama memiliki indeks 0 dan elemen terakhir memiliki indeks Length - 1
(di mana Length
jumlah item dalam array) jadi kode ini tidak berfungsi:
array[array.Length] = 0;
Selain itu perlu diketahui bahwa jika Anda memiliki array multidimensi maka Anda tidak dapat menggunakan Array.Length
kedua dimensi tersebut, 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 kami membuat array bidimensi mentah dari Color
. Setiap item mewakili piksel, indeks berasal dari (0, 0)
hingga (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 ArgumentOutOfRangeException
kode ini (misalnya jika Anda menggunakan GetPixel
metode di Bitmap
kelas).
Array Tidak Tumbuh
Array cepat. Sangat cepat dalam penelusuran linier dibandingkan dengan setiap koleksi lainnya. Ini karena item berdekatan dalam memori sehingga alamat memori dapat dihitung (dan kenaikan hanyalah tambahan). Tidak perlu mengikuti daftar node, matematika sederhana! Anda membayar ini dengan batasan: mereka tidak dapat tumbuh, jika Anda membutuhkan lebih banyak elemen Anda perlu mengalokasikan kembali array itu (ini mungkin membutuhkan 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 sudah ada:
Array.Resize(ref array, array.Length + 1);
Jangan lupa bahwa indeks yang valid berasal dari 0
hingga Length - 1
. Jika Anda hanya mencoba untuk menetapkan item di Length
Anda akan mendapatkan IndexOutOfRangeException
(perilaku ini mungkin membingungkan Anda jika Anda pikir mereka mungkin meningkat dengan sintaks yang mirip dengan Insert
metode koleksi lain).
Array Khusus Dengan Batas Bawah Kustom
Item pertama dalam larik selalu memiliki indeks 0 . Ini tidak selalu benar karena Anda bisa membuat array dengan batas bawah kustom:
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 Tak Terduga
Pengecualian ini dapat dilemparkan karena alasan lain juga: menurut konvensi, banyak fungsi pencarian akan mengembalikan -1 (nullables telah diperkenalkan dengan .NET 2.0 dan bagaimanapun itu juga konvensi terkenal yang digunakan selama bertahun-tahun) jika mereka tidak melakukannya ' tidak menemukan apapun. Misalkan 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 di myArray
akan memenuhi kondisi pencarian karena Array.IndexOf()
akan mengembalikan -1 dan kemudian akses array akan dibuang.
Contoh selanjutnya adalah contoh naif untuk menghitung kemunculan sekumpulan 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 buruk tetapi yang ingin saya tunjukkan adalah bahwa itu akan gagal untuk angka negatif dan angka di atas maximum
.
Bagaimana itu berlaku List<T>
?
Kasus yang sama seperti larik - rentang indeks yang valid - 0 ( List
indeks selalu dimulai dengan 0) untuk list.Count
- mengakses elemen di luar rentang ini akan menyebabkan pengecualian.
Perhatikan bahwa List<T>
melempar ArgumentOutOfRangeException
untuk kasus yang sama di mana array digunakan IndexOutOfRangeException
.
Tidak seperti array, List<T>
dimulai dengan kosong - jadi mencoba mengakses item dari daftar yang baru saja dibuat akan 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 and Columns
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 membuang IndexOutOfRangeException
karena kumpulan data Anda hanya memiliki dua kolom tetapi Anda mencoba untuk mendapatkan nilai dari yang ketiga (indeks selalu berbasis 0).
Harap perhatikan bahwa perilaku ini dibagikan dengan sebagian besar IDataReader
implementasi ( SqlDataReader
, OleDbDataReader
dan seterusnya).
Anda juga bisa mendapatkan pengecualian yang sama jika Anda menggunakan kelebihan IDataReader dari operator pengindeks yang menggunakan nama kolom dan meneruskan nama kolom yang tidak valid.
Misalkan misalnya Anda telah mengambil kolom bernama Column1 tetapi kemudian Anda mencoba untuk mengambil nilai bidang itu dengan
var data = dr["Colum1"]; // Missing the n in Column1.
Ini terjadi karena operator pengindeks diimplementasikan mencoba mengambil indeks bidang Colum1 yang tidak ada. Metode GetOrdinal akan membuang pengecualian ini ketika kode pembantu internalnya mengembalikan -1 sebagai indeks "Colum1".
Lainnya
Ada kasus lain (didokumentasikan) ketika pengecualian ini dilemparkan: jika, dalam DataView
, nama kolom data yang diberikan ke DataViewSort
properti tidak valid.
Bagaimana Menghindari
Dalam contoh ini, izinkan saya berasumsi, untuk kesederhanaan, bahwa array selalu monodimensi dan berbasis 0. Jika Anda ingin ketat (atau Anda sedang mengembangkan perpustakaan), Anda mungkin perlu mengganti 0
dengan GetLowerBound(0)
dan .Length
dengan GetUpperBound(0)
(tentu saja jika Anda memiliki parameter tipe System.Arra
y, itu tidak berlaku untuk T[]
). Harap dicatat bahwa dalam kasus ini, batas atas sudah termasuk 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 dicatat bahwa ini tidak diperbolehkan (itu akan melempar InvalidCastException
), itu sebabnya jika parameter T[]
Anda aman tentang array batas bawah kustom:
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 }));
}
Validasi Parameter
Jika indeks berasal dari parameter Anda harus selalu memvalidasinya (melempar sesuai ArgumentException
atau ArgumentOutOfRangeException
). Dalam contoh berikutnya, parameter yang salah dapat menyebabkan IndexOutOfRangeException
, pengguna fungsi ini mungkin mengharapkan ini karena mereka meneruskan larik tetapi tidak selalu begitu jelas. Saya menyarankan 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 dapat mengganti if
logika dengan Debug.Assert()
:
Debug.Assert(from >= 0 && from < array.Length);
Periksa
indeks Object State Array mungkin tidak datang langsung dari parameter. Ini mungkin bagian dari status objek. Secara umum, selalu merupakan praktik yang baik untuk memvalidasi status objek (dengan sendirinya dan dengan parameter fungsi, jika diperlukan). Anda dapat menggunakan Debug.Assert()
, memberikan pengecualian yang sesuai (lebih deskriptif tentang masalah) atau menanganinya 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 Return Values
Dalam salah satu contoh sebelumnya kita langsung menggunakan Array.IndexOf()
nilai return. Jika kita tahu itu mungkin gagal maka lebih baik menangani kasus itu:
int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
Cara Debug
Menurut pendapat saya, sebagian besar pertanyaan, di SO, tentang kesalahan ini dapat dihindari. Waktu yang Anda habiskan untuk menulis pertanyaan yang tepat (dengan contoh kerja kecil dan penjelasan kecil) bisa 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 tetapi itu benar-benar harus dibaca .
Anda memiliki kode sumber, Anda memiliki pesan pengecualian dengan pelacakan tumpukan. Pergi ke sana, pilih nomor baris yang benar dan Anda akan melihat:
array[index] = newValue;
Anda menemukan kesalahan Anda, periksa bagaimana index
meningkat. Apakah tepat? Periksa bagaimana array dialokasikan, apakah koheren dengan bagaimana index
meningkat? Apakah sesuai dengan spesifikasi Anda? Jika Anda menjawab ya untuk semua pertanyaan ini, Anda akan menemukan bantuan yang baik di sini di StackOverflow, tetapi harap periksa dulu sendiri. Anda akan menghemat waktu Anda sendiri!
Titik awal yang baik adalah selalu menggunakan pernyataan dan memvalidasi masukan. Anda bahkan mungkin ingin menggunakan kontrak kode. Ketika ada yang tidak beres dan Anda tidak dapat mengetahui apa yang terjadi dengan melihat sekilas kode Anda, maka Anda harus menggunakan teman lama: debugger . Jalankan saja aplikasi Anda di debug di dalam Visual Studio (atau IDE favorit Anda), Anda akan melihat dengan tepat baris mana yang menampilkan pengecualian ini, array mana yang terlibat dan indeks mana yang Anda coba gunakan. Sungguh, 99% kali 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 dapat bertaruh).
Sisi cerita VB.NET
Segala sesuatu yang kami katakan di jawaban C # berlaku untuk VB.NET dengan perbedaan sintaks yang jelas tetapi ada hal penting yang perlu dipertimbangkan ketika Anda berurusan dengan array VB.NET.
Di VB.NET, array dideklarasikan menyetel nilai indeks valid maksimum untuk array. Ini 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 menurut 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 ditentukan dalam deklarasi array
Penjelasan sederhana tentang apa itu Indeks di luar pengecualian terikat:
Bayangkan satu kereta ada kompartemennya adalah D1, D2, D3. Seorang penumpang datang untuk masuk kereta dan dia memiliki tiket D4. sekarang apa yang akan terjadi. Penumpang ingin masuk ke kompartemen yang tidak ada sehingga jelas akan timbul masalah.
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]
sudah ada. Jika kita mencoba mengakses array[3]
, sebenarnya tidak ada, jadi indeks di luar pengecualian terikat akan muncul.
Untuk memahami masalahnya dengan mudah, bayangkan kita 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 for-loop loop 4 kali (0, 1, 2 dan 3).
Jadi ketika mencoba mengakses di luar batas dengan (3) itu melempar pengecualian.
Sebuah sisi dari jawaban diterima lengkap yang sangat lama ada poin penting untuk dibuat tentang IndexOutOfRangeException
dibandingkan dengan banyak jenis pengecualian lainnya, dan itu adalah:
Seringkali ada status program yang kompleks yang mungkin sulit untuk dikendalikan pada titik tertentu dalam kode misalnya koneksi DB turun sehingga data untuk input tidak dapat diambil dll ... Masalah semacam ini sering menghasilkan semacam Pengecualian yang harus menggelembung ke tingkat yang lebih tinggi karena di mana hal itu terjadi tidak ada cara untuk mengatasinya pada saat itu.
IndexOutOfRangeException
umumnya berbeda karena dalam banyak kasus cukup mudah untuk memeriksa pada titik di mana pengecualian dimunculkan. Umumnya pengecualian semacam ini dilemparkan oleh beberapa kode yang dapat dengan mudah menangani masalah di tempat terjadinya - hanya dengan memeriksa panjang sebenarnya dari 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 menjelaskan hal ini adalah bahwa pengecualian lain dapat muncul karena kurangnya kontrol atas input atau status program TETAPI IndexOutOfRangeException
lebih sering daripada tidak hanya kesalahan pilot (programmer).