IndexOutOfRangeException / ArgumentOutOfRangeException nedir ve nasıl düzeltirim?


191

Bazı kodlarım var ve çalıştırıldığında, atar IndexOutOfRangeException,

Dizin, dizinin sınırları dışındaydı.

Bu ne anlama geliyor ve bu konuda ne yapabilirim?

Kullanılan sınıflara bağlı olarak, ArgumentOutOfRangeException

Mscorlib.dll dosyasında 'System.ArgumentOutOfRangeException' türü istisnası oluştu, ancak kullanıcı kodunda işlenmedi Ek bilgi: Dizin aralık dışında. Negatif olmamalı ve koleksiyonun boyutundan küçük olmalıdır.


Koleksiyonunuzda sadece 4 öğe varsa, ancak kod 5 dizininde bir öğe almaya çalıştı. Bu IndexOutOfRangeException atar. Kontrol endeksi = 5; if (items.Length> = dizin) Console.WriteLine (intems [dizin]);
Babu Kumarasamy

Yanıtlar:


232

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 IndexOutOfRangeExceptionatı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( Lengthdizideki toplam öğe sayısı) sahip olduğu anlamına gelir;

array[array.Length] = 0;

Ayrıca, çok boyutlu bir diziniz varsa, Array.Lengthher 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 ArgumentOutOfRangeExceptionbu kodu alabilirsiniz (örneğin GetPixelbir Bitmapsı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 0için Length - 1. Sadece bir öğeyi atamak çalışırsanız Lengthalacağınız IndexOutOfRangeException(eğer onlar benzer bir sözdizimi ile artabilir düşünüyorsanız bu davranış sizi şaşırtmak olabilir Insertdiğ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 myArrayarama 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 ArgumentOutOfRangeExceptiondiziler 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 IndexOutOfRangeExceptionEğ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 IDataReaderuygulamaları ( SqlDataReader, OleDbDataReadervb).

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 DataViewsağlanan veri sütun adı DataViewSortgeç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 0ile GetLowerBound(0)ve .Lengthile GetUpperBound(0)(tabii ki tip parametrelerini varsa System.Array, 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 ArgumentExceptionveya 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, ifmantığı 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 indexarttığını kontrol et . Doğru mu? Dizinin nasıl tahsis edildiğini kontrol edin, indexartış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


19

İlişkili istisna dışında bir Endeksin ne olduğu hakkında basit açıklama:

Bir trenin bölmelerinin D1, D2, D3 olduğunu düşünün. Bir yolcu trene girdi ve D4 için bilet aldı. şimdi ne olacak. yolcu var olmayan bir bölmeye girmek istiyor, bu yüzden sorun çıkacak.

Aynı senaryo: bir dizi listesine, vb. Erişmeye çalıştığımızda, yalnızca dizideki mevcut dizinlere erişebiliriz. array[0]ve array[1]var. Erişmeye çalışırsak array[3], aslında orada değildir, bu nedenle bağlı istisna dışında bir dizin ortaya çıkar.


10

Sorunu kolayca anlamak için bu kodu yazdığımızı düşünün:

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

Sonuç:

hello1
hello2
hello3

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

Dizinin boyutu 3'tür (indeks 0, 1 ve 2), ancak for-loop 4 kez döngü yapar (0, 1, 2 ve 3).
Bu nedenle (3) ile sınırların dışına erişmeye çalıştığında istisna atar.


1

Kabul edilen çok uzun bir cevaptan bir taraf, IndexOutOfRangeExceptiondiğer birçok istisna türüyle karşılaştırıldığında dikkate alınması gereken önemli bir nokta var ve bu:

Genellikle kodda belirli bir noktada kontrol edilmesi zor olan karmaşık bir program durumu vardır, örneğin bir DB bağlantısı kesilir, böylece bir giriş için veri alınamaz vb. Bu tür bir sorun genellikle daha yüksek bir seviyeye kadar kabarmak zorunda kaldığı için, bu noktada onunla başa çıkmanın bir yolu yoktur.

IndexOutOfRangeExceptionçoğu durumda istisnanın ortaya çıktığı noktada kontrol edilmesi oldukça önemsiz olduğundan farklıdır. Genellikle bu tür bir istisna, sadece dizinin gerçek uzunluğunu kontrol ederek, meydana geldiği yerde sorunla kolayca başa çıkabilen bazı kodlar tarafından atılır. Bunu, bu istisnayı daha yükseğe kaldırarak 'düzeltmek' istemezsiniz - bunun yerine ilk aşamada atılmamasını sağlayarak - çoğu durumda dizi uzunluğunu kontrol ederek bunu yapmak kolaydır.

Bunu koymanın bir başka yolu, giriş veya program durumu üzerinde gerçek kontrol eksikliği nedeniyle diğer istisnaların ortaya çıkabileceğidir, ancak AMA IndexOutOfRangeExceptionsadece pilot (programcı) hatası değildir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.