1. ホーム
  2. c#

[解決済み] IndexOutOfRangeException / ArgumentOutOfRangeExceptionとは何ですか、またどのように修正すればよいですか?

2022-02-18 12:59:28

質問内容

あるコードがあり、実行時に IndexOutOfRangeException と言っている。

インデックスが配列の境界の外にありました。

これはどういうことですか、どうすればいいのですか?

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

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

どのように解決するのですか?

それは何ですか?

この例外は、コレクションアイテムにインデックスでアクセスしようとしたときに、無効なインデックスを使用したことを意味します。インデックスがコレクションの下限値より小さいか、 含まれる要素の数より大きいか等しい場合、インデックスが無効となります。

スローされる場合

と宣言された配列が与えられる。

byte[] array = new byte[4];

この配列には0から3までアクセスでき、この範囲外の値では IndexOutOfRangeException が投げられます。配列を作成したりアクセスしたりするときは、このことを忘れないでください。

配列の長さ
C#では、通常、配列は0ベースです。つまり、最初の要素のインデックスが0、最後の要素のインデックスが Length - 1 (ここで Length は配列の総項目数)なので、このコードはうまくいきません。

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

上界は包括的ではない
次の例では、生の二次元配列である 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 このコードに対して(たとえば GetPixel メソッドを Bitmap クラス)を作成します。

配列は成長しない
配列は高速です。他のあらゆるコレクションと比較して、線形探索が非常に速いのです。これは、メモリ上で項目が連続しているので、メモリアドレスが計算できるためです(インクリメントは単なる足し算です)。ノードリストに従う必要はなく、単純な計算でOK! もし、もっと多くの要素が必要なら、その配列を再割り当てする必要があります(古いアイテムを新しいブロックにコピーしなければならない場合は、比較的長い時間がかかるかもしれません)。配列のサイズを変更するには 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 (nullables は .NET 2.0 で導入されましたが、とにかく長年にわたって使用されている有名な規約です) を返します。文字列と比較可能なオブジェクトの配列があるとします。このようなコードを書こうと思うかもしれません。

// 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 の実装( SqlDataReader , OleDbDataReader など)。

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

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

のインデックスを取得しようとするインデクサ演算子が実装されているため、このような現象が発生します。 Colum1 フィールドが存在しないことを示します。GetOrdinal メソッドは、その内部ヘルパー・コードが "Colum1" のインデックスとして -1 を返すと、この例外をスローします。

その他
この例外が発生する(文書化されている)ケースはもう一つあります。 DataView に供給されているデータカラム名が DataViewSort プロパティは有効ではありません。

回避方法

この例では、簡単のために、配列は常に一次元で、0ベースであると仮定してみます。厳密を期したい場合(あるいはライブラリを開発している場合)には、配列の値を 0GetLowerBound(0).LengthGetUpperBound(0) (もちろん System.Arra y の場合は適用されません。 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];
        }
}

戻り値の有効化
前の例の一つでは、直接 Array.IndexOf() の戻り値です。もし失敗することが分かっているのなら、そのようなケースに対処する方がよいでしょう。

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

デバッグの方法

私の意見では、このエラーに関するSOでの質問のほとんどは、単に回避することができます。適切な質問(小さな動作例と小さな説明付き)を書くために費やす時間は、あなたのコードをデバッグするために必要な時間よりもずっと長いかもしれません。まず最初に、Eric Lippertのブログの記事を読んでください。 小規模プログラムのデバッグ 彼の言葉をここで繰り返すことはしませんが、これは本当に重要なことです。 必読 .

ソースコードがあり、スタックトレース付きの例外メッセージがあります。そこに行って、正しい行番号を選べば、見ることができます。

array[index] = newValue;

エラーが見つかりました。 index が増えます。それは正しいのでしょうか?配列がどのように割り当てられるかを確認し、その方法が index を増やすことができますか?あなたの仕様で正しいですか?もし イエス しかし、まずはご自身で確認してみてください。自分の時間を節約することができますよ。

良いスタートポイントは、常にアサーションを使用し、入力の検証を行うことです。コードコントラクトを使用することもできます。何かが間違っていて、コードをざっと見ただけでは何が起こっているのかわからないときは、古い友人に頼るしかないでしょう。 デバッガ . 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

つまり,このループでは,配列に5つの整数を埋めても IndexOutOfRangeException

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

VB.NETのルール

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