IndexOutOfRangeException / ArgumentOutOfRangeExceptionとは何ですか。どうすれば修正できますか?


191

私はいくつかのコードを持っていますが、実行するとがスローされIndexOutOfRangeException

インデックスが配列の範囲外だった。

これはどういう意味ですか。どうすればよいですか。

使用するクラスによっては、 ArgumentOutOfRangeException

タイプ 'System.ArgumentOutOfRangeException'の例外がmscorlib.dllで発生しましたが、ユーザーコードでは処理されませんでした追加情報:インデックスが範囲外でした。負ではなく、コレクションのサイズ未満である必要があります。


アイテムが4つしかなく、コードがインデックス5のアイテムを取得しようとした場合、コレクションではIndexOutOfRangeExceptionがスローされます。チェックインデックス= 5; if(items.Length> = index)Console.WriteLine(intems [index]);
Babu Kumarasamy

回答:


231

それは何ですか?

この例外は、無効なインデックスを使用して、インデックスでコレクションアイテムにアクセスしようとしていることを意味します。インデックスは、コレクションの下限よりも低いか、含まれている要素の数以上の場合は無効です。

投げられたとき

次のように宣言された配列があるとします。

byte[] array = new byte[4];

この配列には0〜3の範囲でアクセスできます。この範囲外の値は IndexOutOfRangeException。がスローされます。アレイを作成してアクセスするときは、このことを忘れないでください。

配列の長さ
C#では、通常、配列は0ベースです。これは、最初の要素にインデックス0があり、最後の要素にインデックスLength - 1Length配列内の項目の総数)があることを意味するため、次のコードは機能しません。

array[array.Length] = 0;

さらに、多次元配列がある場合は、Array.Length両方の次元に使用できないため、次のように使用する必要があることに注意して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;
    }
}

上限が含まれない
次の例では、の生の2次元配列を作成しますColor。各アイテムはピクセルを表し、インデックスは(0, 0)(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;
    }
}

このコードは、配列が0ベースで、画像の最後(右下)のピクセルが次の理由で失敗しますpixels[imageWidth - 1, imageHeight - 1]

pixels[imageWidth, imageHeight] = Color.Black;

別のシナリオではArgumentOutOfRangeException、このコードを取得できます(たとえばGetPixelBitmapクラスでメソッドを使用している場合)。

配列が大きくならない
配列は高速です。他のすべてのコレクションと比較して、線形検索は非常に高速です。これは、メモリ内でアイテムが隣接しているため、メモリアドレスを計算できるためです(増分は単なる加算です)。ノードリストをたどる必要はありません、簡単な数学!制限付きでこれを支払います。要素を増やす必要がある場合は、配列を再割り当てする必要があります(古いアイテムを新しいブロックにコピーする必要がある場合、これには比較的長い時間がかかる可能性があります)。でサイズを変更しますArray.Resize<T>()。この例では、既存の配列に新しいエントリを追加します。

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

有効なインデックスはからである0ことを忘れないでくださいLength - 1。単にアイテムを割り当てようとした場合Length、あなたが得られますIndexOutOfRangeException(あなたは、彼らがに似た構文で増加すると思われる場合、この動作はあなたを混乱させる可能性Insert他のコレクションの方法)。

配列のカスタム下限の最初のアイテムを持つ特別な
配列は常にインデックス0を持ちます。カスタムの下限で配列を作成できるため、これは常に当てはまるわけではありません。

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

この例では、配列のインデックスは1から4まで有効です。もちろん、上限は変更できません。

誤った引数
(ユーザー入力または関数ユーザーから)検証されていない引数を使用して配列にアクセスすると、次のエラーが発生することがあります。

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

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

予期しない結果
この例外は別の理由でもスローされる可能性があります。慣例により、多くの検索関数は-1を返します(nullableは.NET 2.0で導入されており、とにかく長年使用されているよく知られた慣例でもあります)。 t何かを見つけます。文字列に相当するオブジェクトの配列があるとしましょう。あなたはこのコードを書くことを考えるかもしれません:

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

myArray検索条件を満たすアイテムがない場合、これは失敗します。Array.IndexOf() -1を返し、配列アクセスがスローます。

次の例は、与えられた数のセットの出現を計算する単純な例です(最大数を知っており、配列0のアイテムが番号0を表し、インデックス1のアイテムが数1を表すというように配列を返します)。

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

もちろん、それはかなりひどい実装ですが、私が示したいのは、負の数と上記の数では失敗するということです maximumです。

それがどのように適用されるか List<T>ますか?

配列と同じ場合-有効なインデックスの範囲-0(Listのインデックスは常に0で始まります)〜list.Count -この範囲外の要素にアクセスすると例外が発生します。

配列が使用するのと同じケースでList<T>スローArgumentOutOfRangeExceptionすることに注意してくださいIndexOutOfRangeException

配列とは異なり、List<T>空から始まります-作成したばかりのリストのアイテムにアクセスしようとすると、この例外が発生します。

var list = new List<int>();

一般的なケースは、リストにインデックスを設定することです(と同様Dictionary<int, T>)で例外が発生します。

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

IDataReaderと列
次のコードを使用して、データベースからデータを読み取ろうとしているとします。

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()IndexOutOfRangeExceptionデータセットには2つの列しかないが、3番目の列から値を取得しようとしているため、スローされます(インデックスは常に 0ベースです)。

この動作は、ほとんどのIDataReader実装(SqlDataReaderOleDbDataReader)。

列名を受け取り、無効な列名を渡すインデクサー演算子のIDataReaderオーバーロードを使用した場合にも、同じ例外が発生する可能性があります。
たとえば、Column1という名前の列を取得したが、そのフィールドの値を取得しようとしたとします。

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

これは、存在しないColum1フィールドのインデックスを取得しようとしてインデクサーオペレーターが実装されているために発生します。GetOrdinalメソッドは、その内部ヘルパーコードが「Colum1」のインデックスとして-1を返すと、この例外をスローします。

その他
この例外がスローされる別の(文書化された)ケースがあります:でDataViewDataViewSortプロパティに提供されているデータ列名が無効な場合。

回避する方法

この例では、簡単にするために、配列は常に1次元で0ベースであると仮定します。あなたは、厳密な(またはライブラリを開発している)になりたい場合は、交換する必要があるかもしれない0GetLowerBound(0)して.LengthGetUpperBound(0)(あなたがタイプのパラメータを持っている場合はもちろんSystem.Arrayは、それが適用されませんT[])。この場合、上限はこのコードを含むことに注意してください:

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

このように書き直す必要があります:

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

これは許可されていないことに注意してください(これはをスローしますInvalidCastException)。これが、パラメーターがT[]カスタム下限配列について安全である場合の理由です。

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

パラメータの検証
インデックスがパラメータからのものである場合は、常にそれらを検証する必要があります(適切なArgumentExceptionまたはをスローしますArgumentOutOfRangeException)。次の例では、誤ったパラメーターが原因IndexOutOfRangeExceptionである可能性があります。この関数のユーザーは、配列を渡しているためにこれを予期する可能性がありますが、必ずしもそれほど明白ではありません。常にパブリック関数のパラメーターを検証することをお勧めします。

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

関数がプライベートの場合、ifロジックを単に次のように置き換えることができますDebug.Assert()

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

オブジェクト状態
配列のインデックスがパラメータから直接取得されない可能性があることを確認します。オブジェクトの状態の一部である可能性があります。一般に、オブジェクトの状態を検証するためには常に(それ自体、および必要に応じて関数パラメーターを使用して)常に良い方法です。を使用しDebug.Assert()たり、適切な例外をスローしたり(問題についてより詳細に説明したり)、次の例のように処理したりできます。

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

戻り値の検証
前の例の1 つでは、戻り値を直接使用しましたArray.IndexOf()。失敗する可能性があることがわかっている場合は、そのケースを処理することをお勧めします。

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

デバッグする方法

私の意見では、このエラーに関するほとんどの質問(ここではSO)は単純に回避できます。適切な質問を書くために費やす時間(小さな作業例と小さな説明を含む)は、コードのデバッグに必要な時間よりもはるかに簡単です。まず、小さなプログラムのデバッグに関するこのEric Lippertのブログ投稿を読んでください。ここで彼の言葉を繰り返すことはしませんが、必ず読む必要があります

ソースコードがあり、スタックトレース付きの例外メッセージがあります。そこに行って、正しい行番号を選択すると、次のように表示されます。

array[index] = newValue;

エラーが見つかりましたindex。増加率を確認してください。正しいですか?配列の割り当て方法を確認してくださいindex。仕様通りですか?はいと答えたらこれらすべての質問にと、StackOverflowで役立つヘルプが見つかりますが、まず自分で確認してください。自分の時間を節約できます!

良い出発点は、常にアサーションを使用し、入力を検証することです。コードコントラクトを使用することもできます。何かがうまくいかず、コードをざっと見ても何が起こるかわからない場合は、昔の友達であるデバッガに頼らなければなりません。Visual Studio(またはお気に入りのIDE)内のデバッグでアプリケーションを実行するだけで、どの行がこの例外をスローし、どの配列が関係し、どのインデックスを使用しようとしているのかが正確にわかります。実際には、99%の時間で自分で解決できます。

これが本番環境で発生する場合は、差別されたコードにアサーションを追加することをお勧めします。おそらく、自分では見えないものはコードに表示されません(ただし、常に賭けることはできます)。

ストーリーのVB.NET側

C#の回答で述べたことはすべて、VB.NETにも有効ですが、構文に明らかな違いがありますが、VB.NET配列を扱う際には、考慮すべき重要な点があります。

VB.NETでは、配列は配列の有効な最大インデックス値を設定して宣言されます。配列に格納する要素の数ではありません。

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

したがって、このループは、IndexOutOfRangeExceptionを発生させることなく、配列を5つの整数で埋めます。

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

VB.NETルール

この例外は、無効なインデックスを使用して、インデックスでコレクションアイテムにアクセスしようとしていることを意味します。インデックスがコレクションの下限よりも低いか、またはより大きい場合、インデックスは無効ですそれが含む要素の数に等しい。 配列宣言で定義された最大許容インデックス


19

インデックスの範囲外の例外についての簡単な説明:

1つの列車のコンパートメントがD1、D2、D3であると考えてください。一人の乗客が電車に入るようになり、彼はD4のチケットを持っています。今何が起こるか。乗客は存在しないコンパートメントに入ろうとするため、明らかに問題が発生します。

同じシナリオ:配列リストなどにアクセスしようとするときはいつでも、配列内の既存のインデックスにしかアクセスできません。array[0]そしてarray[1]存在しています。にアクセスしようとするとarray[3]、実際にはありません。そのため、インデックスの範囲外の例外が発生します。


10

問題を簡単に理解するために、次のコードを書いたとします。

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

結果は次のようになります。

hello1
hello2
hello3

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

配列のサイズは3(インデックス0、1、2)ですが、forループは4回ループします(0、1、2、3)。
したがって、(3)で境界の外にアクセスしようとすると、例外がスローされます。


1

非常に長い完全に受け入れられた回答IndexOutOfRangeExceptionとは別に、他の多くの例外タイプと比較して、注意すべき重要な点があります。それは次のとおりです。

多くの場合、コードの特定のポイントで制御するのが難しい可能性のある複雑なプログラム状態があります。たとえば、DB接続がダウンして、入力のデータを取得できないなどです。それが発生する場所にはその時点でそれを処理する方法がないため、より高いレベルにバブルアップする必要があります。

IndexOutOfRangeExceptionほとんどの場合、例外が発生しているポイントで確認するのは簡単です。一般に、この種の例外は、配列の実際の長さを確認するだけで、問題が発生している場所で問題に非常に簡単に対処できるコードによってスローされます。この例外を上位に処理することでこれを「修正」したくない-代わりに、最初のインスタンスで例外がスローされないようにする-これは、ほとんどの場合、配列の長さをチェックすることで簡単に実行できます。

これを説明する別の方法は、入力またはプログラムの状態に対する制御の純粋な欠如が原因で他の例外が発生する可能性があることIndexOutOfRangeExceptionですが、単なるパイロット(プログラマー)エラーではないことがよくあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.