Bu ne?
Bu kural dışı durum, bir koleksiyon öğesine geçersiz bir dizin kullanarak dizine göre erişmeye çalıştığınız anlamına gelir. Dizin, koleksiyonun alt sınırından düşük veya içerdiği öğe sayısından büyük veya ona eşit olduğunda geçersizdir.
Atıldığında
Şu şekilde bildirilen bir dizi verildi:
byte[] array = new byte[4];
Bu diziye 0 ila 3 arasında erişebilirsiniz, bu aralığın dışındaki değerlerin IndexOutOfRangeException
atılmasına neden olur . Bir dizi oluşturup eriştiğinizde bunu unutmayın.
Dizi Uzunluğu
C # 'da genellikle diziler 0 tabanlıdır. Bu, ilk öğenin dizin 0'a ve son öğenin dizine Length - 1
( Length
dizideki toplam öğe sayısı) sahip olduğu anlamına gelir;
array[array.Length] = 0;
Ayrıca, çok boyutlu bir diziniz varsa, Array.Length
her iki boyut için kullanamayacağınızı lütfen unutmayın 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;
}
}
Üst Sınır Dahil Değildir
Aşağıdaki örnekte, bir ham iki boyutlu dizi oluşturuyoruz Color
. Her öğe bir pikseli temsil eder endeksler gelmektedir (0, 0)
için (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;
}
}
Dizi 0 tabanlı ve görüntüdeki son (sağ alt) piksel olduğundan bu kod başarısız olur pixels[imageWidth - 1, imageHeight - 1]
:
pixels[imageWidth, imageHeight] = Color.Black;
Başka bir senaryoda ArgumentOutOfRangeException
bu kodu alabilirsiniz (örneğin GetPixel
bir Bitmap
sınıfta yöntem kullanıyorsanız ).
Diziler Büyümez
Bir dizi hızlıdır. Diğer tüm koleksiyonlara kıyasla doğrusal aramada çok hızlı. Bunun nedeni, öğelerin bellekte bitişik olmasıdır, böylece bellek adresi hesaplanabilir (ve artış yalnızca bir ektir). Bir düğüm listesini takip etmeye gerek yok, basit matematik! Bunu bir sınırlama ile ödersiniz: büyüyemezler, daha fazla öğeye ihtiyacınız varsa bu diziyi yeniden tahsis etmeniz gerekir (eski öğelerin yeni bir bloğa kopyalanması gerekiyorsa bu nispeten uzun zaman alabilir). Bunları yeniden boyutlandırdığınızda Array.Resize<T>()
, bu örnek mevcut bir diziye yeni bir giriş ekler:
Array.Resize(ref array, array.Length + 1);
Geçerli endeksleri gelmektedir Unutmayın 0
için Length - 1
. Sadece bir öğeyi atamak çalışırsanız Length
alacağınız IndexOutOfRangeException
(eğer onlar benzer bir sözdizimi ile artabilir düşünüyorsanız bu davranış sizi şaşırtmak olabilir Insert
diğer koleksiyonların yöntemiyle).
Özel Alt Sınırlı Özel Diziler Dizilerdeki
ilk öğe her zaman 0 dizinine sahiptir . Özel bir alt sınır içeren bir dizi oluşturabileceğiniz için bu her zaman doğru değildir:
var array = Array.CreateInstance(typeof(byte), new int[] { 4 }, new int[] { 1 });
Bu örnekte, dizi indeksleri 1'den 4'e kadar geçerlidir. Tabii ki, üst sınır değiştirilemez.
Yanlış Bağımsız Değişkenler
Bir diziye onaylanmamış bağımsız değişkenler kullanarak erişirseniz (kullanıcı girdisinden veya işlev kullanıcıdan) şu hatayı alabilirsiniz:
private static string[] RomanNumbers =
new string[] { "I", "II", "III", "IV", "V" };
public static string Romanize(int number)
{
return RomanNumbers[number];
}
Beklenmedik Sonuçlar
Bu istisna başka bir nedenle de atılabilir: kural olarak, birçok arama işlevi -1 döndürür (nullables .NET 2.0 ile tanıtılmıştır ve yine de uzun yıllardan beri kullanılan iyi bilinen bir kuraldır). hiçbir şey bulamıyorum. Bir dizeyle karşılaştırılabilir bir dizi nesneye sahip olduğunuzu düşünelim. Bu kodu yazmayı düşünebilirsiniz:
// 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")]);
-1 döndürecek ve sonra dizi erişimi atacağı için hiçbir öğe myArray
arama koşulunu karşılamazsa bu başarısız olur Array.IndexOf()
.
Sonraki örnek, belirli bir sayı kümesinin oluşumlarını hesaplamak için naif bir örnektir (maksimum sayıyı bilmek ve dizin 0'daki öğenin 0 sayısını, dizin 1'deki öğeler 1 sayısını temsil ettiği vb. Bir dizi döndürme):
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;
}
Tabii ki, oldukça korkunç bir uygulama ama göstermek istediğim, yukarıdaki negatif sayılar ve sayılar için başarısız olacağı maximum
.
Nasıl uygulanır List<T>
?
Geçerli dizinlerin dizi aralığıyla aynı durumlar - 0 ( List
'ın dizinleri her zaman 0 ile başlar) list.Count
- bu aralığın dışındaki öğelere erişmek istisnayı oluşturur.
Not List<T>
atar ArgumentOutOfRangeException
diziler kullanmak aynı durumlar için IndexOutOfRangeException
.
Dizilerden farklı olarak List<T>
boş başlar - bu nedenle yeni oluşturulan listenin öğelerine erişmeye çalışmak bu istisnayı doğurur.
var list = new List<int>();
Yaygın durum, listeyi indeksleme ile doldurmaktır (buna benzer Dictionary<int, T>
) istisnaya neden olur:
list[0] = 42; // exception
list.Add(42); // correct
IDataReader ve Sütunlar
Bu kodla bir veritabanındaki verileri okumaya çalıştığınızı düşünün:
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()
atacağım IndexOutOfRangeException
Eğer veri kümesi sadece iki sütun bulunur ama 3 birinden bir değer elde etmek için çalışıyoruz çünkü (endeksleri olan her zaman 0 tabanlı).
Bu davranış çoğu ile paylaşılır unutmayınız IDataReader
uygulamaları ( SqlDataReader
, OleDbDataReader
vb).
Aynı istisnayı, bir sütun adı alan ve geçersiz bir sütun adı geçiren dizin oluşturucu işlecinin IDataReader aşırı yükünü kullanırsanız da alabilirsiniz.
Diyelim ki Sütun1 adlı bir sütun aldınız, ancak daha sonra bu alanın değerini
var data = dr["Colum1"]; // Missing the n in Column1.
Bunun nedeni, dizin oluşturucu işlecinin, var olmayan bir Colum1 alanının dizinini almaya çalışırken uygulanmasıdır . GetOrdinal yöntemi, iç yardımcı kodu "Sütun1" dizini olarak -1 döndürdüğünde bu özel durumu atar.
Diğerleri
Bu kural dışı durumun atıldığı başka bir (belgelenmiş) durum daha vardır: özelliğe DataView
sağlanan veri sütun adı DataViewSort
geçerli değilse.
Nasıl Kaçının
Bu örnekte, basitlik açısından, dizilerin her zaman tek boyutlu ve 0 tabanlı olduğunu varsayalım. Eğer (veya bir kütüphane geliştiriyorsanız) sıkı olmasını istiyorsanız, değiştirmek gerekebilir 0
ile GetLowerBound(0)
ve .Length
ile GetUpperBound(0)
(tabii ki tip parametrelerini varsa System.Arra
y, ne için geçerli değildir T[]
). Bu durumda, üst sınırın bu koddan dahil olduğunu lütfen unutmayın:
for (int i=0; i < array.Length; ++i) { }
Bu şekilde yeniden yazılmalıdır:
for (int i=array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) { }
Lütfen buna izin verilmediğine (atılacağına InvalidCastException
) dikkat edin, bu nedenle parametreleriniz T[]
özel alt sınır dizileri konusunda güvende iseniz:
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 }));
}
Parametreleri Doğrula
İndeks bir parametreden geliyorsa, bunları her zaman doğrulamanız gerekir (uygun ArgumentException
veya atanarak ArgumentOutOfRangeException
). Bir sonraki örnekte, yanlış parametreler neden olabilir IndexOutOfRangeException
, bu işlevin kullanıcıları bir diziyi geçtikleri için bunu bekleyebilirler, ancak her zaman çok açık değildir. Genel işlevler için parametreleri her zaman doğrulamanızı öneririm:
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);
}
İşlev özelse, if
mantığı basitçe şu şekilde değiştirebilirsiniz Debug.Assert()
:
Debug.Assert(from >= 0 && from < array.Length);
Check Object State
Array indeksi doğrudan bir parametreden gelmeyebilir. Nesne durumunun bir parçası olabilir. Genel olarak nesne durumunu doğrulamak için her zaman iyi bir uygulamadır (kendi başına ve gerekirse işlev parametreleriyle). Kullanabilir, Debug.Assert()
uygun bir istisna atabilir (sorun hakkında daha açıklayıcı) veya bu örnekte olduğu gibi işleyebilirsiniz:
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];
}
}
Dönüş Değerlerini Doğrulama
Önceki örneklerden birinde doğrudan Array.IndexOf()
dönüş değerini kullandık . Başarısız olabileceğini biliyorsanız, bu davayı ele almak daha iyidir:
int index = myArray[Array.IndexOf(myArray, "Debug");
if (index != -1) { } else { }
Hata Ayıklama
Bence, burada SO ile ilgili bu hatayla ilgili soruların çoğu basitçe önlenebilir. Düzgün bir soru yazmak için harcadığınız zaman (küçük bir çalışma örneği ve küçük bir açıklama ile), kodunuzda hata ayıklamak için ihtiyaç duyduğunuz zamandan çok daha kolay olabilir. Her şeyden önce, Eric Lippert'in küçük programlarda hata ayıklama hakkındaki blog yazısını okuyun , sözlerini burada tekrar etmeyeceğim ama kesinlikle bir zorunluluktur .
Kaynak kodunuz var, yığın izlemeli özel durum mesajınız var. Oraya git, doğru satır numarasını seç ve göreceksin:
array[index] = newValue;
Hatayı buldun, nasıl index
arttığını kontrol et . Doğru mu? Dizinin nasıl tahsis edildiğini kontrol edin, index
artışlarla tutarlı mı? Spesifikasyonlarınıza göre doğru mu? Tüm bu sorulara evet yanıtı verirseniz , burada StackOverflow'da iyi bir yardım bulacaksınız, ancak lütfen önce bunu kendiniz kontrol edin. Kendi zamanınızdan tasarruf edeceksiniz!
İyi bir başlangıç noktası, her zaman iddiaları kullanmak ve girdileri doğrulamaktır. Kod sözleşmelerini bile kullanmak isteyebilirsiniz. Bir şeyler ters gittiğinde ve kodunuza hızlı bir bakışla neler olduğunu anlayamadığınızda, eski bir arkadaşınıza başvurmanız gerekir: hata ayıklayıcı . Uygulamanızı Visual Studio (veya en sevdiğiniz IDE) içindeki hata ayıklama ile çalıştırın, tam olarak hangi satırın bu istisnayı attığını, hangi dizinin dahil olduğunu ve hangi dizini kullanmaya çalıştığınızı göreceksiniz. Gerçekten,% 99'unu birkaç dakika içinde kendiniz çözeceksiniz.
Üretimde bu olursa, suçlu kodda iddialar eklemeniz daha iyi olur, muhtemelen kodunuzda kendiniz göremediğiniz şeyleri görmeyiz (ancak her zaman bahis yapabilirsiniz).
Hikayenin VB.NET tarafı
C # cevabında söylediğimiz her şey, belirgin sözdizimi farklarıyla VB.NET için geçerlidir, ancak VB.NET dizileriyle uğraşırken dikkate almanız gereken önemli bir nokta vardır.
VB.NET'te diziler, dizi için maksimum geçerli dizin değeri ayarlanarak bildirilir. Dizide saklamak istediğimiz öğelerin sayısı değildir.
' declares an array with space for 5 integer
' 4 is the maximum valid index starting from 0 to 4
Dim myArray(4) as Integer
Böylece bu döngü, herhangi bir IndexOutOfRangeException özelliğine neden olmadan diziyi 5 tamsayı ile dolduracaktır.
For i As Integer = 0 To 4
myArray(i) = i
Next
VB.NET kuralı
Bu kural dışı durum, bir koleksiyon öğesine geçersiz bir dizin kullanarak dizine göre erişmeye çalıştığınız anlamına gelir. Dizin, koleksiyonun alt sınırından daha düşük veya daha büyük olduğunda geçersizdiriçerdiği eleman sayısına eşittir. dizi bildiriminde tanımlanan izin verilen maksimum dizin