[C#] IndexOutOfRangeException / ArgumentOutOfRangeException은 무엇이며 어떻게 해결합니까?

코드가 있고 실행될 때 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;
    }
}

상위 경계가 포함되지 않음
다음 예에서는의 원시 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이 코드를 얻을 수 있습니다 (예 GetPixel: Bitmap클래스에서 메소드를 사용 하는 경우 ).

배열이 성장하지 않음
배열이 빠릅니다. 다른 모든 컬렉션에 비해 선형 검색에서 매우 빠릅니다. 메모리에 항목이 연속되어 있기 때문에 메모리 주소를 계산할 수 있습니다 (증분은 단지 추가 일 뿐임). 노드 목록을 따를 필요없이 간단한 수학! 더 많은 요소가 필요한 경우 해당 배열을 재 할당해야합니다 (오래된 항목을 새 블록에 복사해야하는 경우에는 비교적 오랜 시간이 걸릴 수 있음). 로 크기를 조정하면 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을 반환합니다 (.NET 2.0에서 nullables가 도입되었으며 어쨌든 수년 동안 잘 알려진 규칙 임). 아무것도 찾을 수 없습니다. 문자열과 비슷한 객체 배열이 있다고 가정 해 봅시다. 이 코드를 작성한다고 생각할 수도 있습니다.

// 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을 반환하고 배열 액세스가 myArray발생하기 때문에 검색 조건을 만족하는 항목이 없으면 실패합니다 Array.IndexOf().

다음 예제는 주어진 숫자 집합의 발생을 계산하는 순진한 예제입니다 (최대 숫자를 알고 인덱스 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데이터 세트에 열이 두 개뿐 이므로 세 번째 열에서 값을 얻으려고 시도합니다 (표시는 항상 0 기반 임).

이 동작은 대부분의 IDataReader구현 과 공유됩니다 ( SqlDataReaderOleDbDataReader).

열 이름을 사용하고 유효하지 않은 열 이름을 전달하는 인덱서 연산자의 IDataReader 오버로드를 사용하는 경우에도 동일한 예외가 발생할 수 있습니다.
예를 들어, Column1 이라는 을 검색했지만 다음을 사용하여 해당 필드의 값을 검색하려고 한다고 가정하십시오.

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

이는 존재하지 않는 Colum1 필드 의 색인을 검색하려고 인덱서 연산자가 구현 되었기 때문에 발생 합니다. 내부 도우미 코드에서 -1을 “Colum1″의 인덱스로 반환하면 GetOrdinal 메서드에서이 예외가 발생합니다.

기타이
예외가 발생하는 경우 (문서화 된) 또 다른 경우가 있습니다. in DataView에서 DataViewSort속성 에 제공되는 데이터 열 이름이 유효하지 않은 경우입니다.

피하는 방법

이 예제에서는 간단히하기 위해 배열이 항상 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);
}

함수가 private 인 경우 단순히 if논리를 Debug.Assert()다음 과 같이 대체 할 수 있습니다 .

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

Check Object State
Array 색인이 매개 변수에서 직접 제공되지 않을 수 있습니다. 객체 상태의 일부일 수 있습니다. 일반적으로 객체 상태를 자체적으로 (필요한 경우 함수 매개 변수를 사용하여) 확인하는 것이 좋습니다. 을 사용하거나 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 { }

디버깅하는 방법

제 생각에,이 오류에 관한 대부분의 질문은 간단하게 피할 수 있습니다. 적절한 질문을 작성하는 데 소요되는 시간 (작은 실례와 작은 설명으로)은 코드를 디버깅하는 데 필요한 시간보다 훨씬 더 많을 수 있습니다. 우선, 작은 프로그램의 디버깅에 관한 Eric Lippert의 블로그 게시물을 읽으십시오 . 여기서 그의 말을 반복하지는 않지만 반드시 읽어야 합니다.

소스 코드가 있고 스택 추적이 포함 된 예외 메시지가 있습니다. 거기에 가서 올바른 줄 번호를 선택하면 다음을 볼 수 있습니다.

array[index] = newValue;

당신은 당신의 오류를 발견 어떻게 index증가 하는지 확인하십시오 . 맞아? 배열이 어떻게 할당되는지 확인하고 어떻게 index증가 하는지 일관성 사양에 따라 맞습니까? 이 모든 질문에 예라고 대답 하면 StackOverflow에 대한 유용한 도움말을 찾을 수 있지만 먼저 직접 확인하십시오. 당신은 당신의 자신의 시간을 절약 할 수 있습니다!

좋은 시작점은 항상 어설 션을 사용하고 입력을 확인하는 것입니다. 코드 계약을 사용할 수도 있습니다. 문제가 발생하여 코드를 빠르게 살펴보면 어떤 일이 발생하는지 파악할 수 없으면 옛 친구 인 debugger 를 사용해야합니다 . 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 규칙

이 예외는 잘못된 인덱스를 사용하여 인덱스로 컬렉션 항목에 액세스하려고 함을 의미합니다. 컬렉션의 하한보다 작거나보다 큰 인덱스는 유효하지 않습니다.포함하는 요소 수와 같습니다. 배열 선언에 정의 된 최대 허용 인덱스


답변

Index out of bound exception이 무엇인지에 대한 간단한 설명 :

한 열차에 구획이 D1, D2, D3이라고 생각하십시오. 한 승객이 기차에 들어 와서 D4 티켓을 받았습니다. 이제 어떻게 될까요. 승객이 존재하지 않는 구획에 들어가기를 원하므로 분명히 문제가 발생합니다.

같은 시나리오 : 배열 목록 등에 액세스하려고 할 때마다 배열의 기존 인덱스에만 액세스 할 수 있습니다. array[0]그리고 array[1]존재합니다. 우리가 액세스하려고하면 array[3]실제로 존재하지 않으므로 바운드 예외 인덱스가 발생합니다.


답변

문제를 쉽게 이해하기 위해 다음 코드를 작성했다고 상상해보십시오.

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-loop는 4 번 (0, 1, 2 및 3) 루프됩니다.
따라서 (3)으로 경계 외부에 액세스하려고하면 예외가 발생합니다.


답변

매우 오랫동안 받아 들여진 대답의 한 측면에서 IndexOutOfRangeException다른 많은 예외 유형과 비교할 때 중요한 점이 있습니다.

종종 코드의 특정 지점에서 제어하기 어려운 복잡한 프로그램 상태가 있습니다. 예를 들어 DB 연결이 끊어 지므로 입력 데이터를 검색 할 수 없습니다. 이런 종류의 문제로 인해 종종 그것이 발생하는 곳은 그 시점에서 그것을 다루는 방법이 없기 때문에 더 높은 수준으로 버블 링해야합니다.

IndexOutOfRangeException대부분의 경우 예외가 발생하는 시점에서 확인하는 것이 매우 간단하다는 점에서 일반적으로 다릅니다. 일반적으로 이러한 종류의 예외는 발생하는 장소에서 문제를 매우 쉽게 처리 할 수있는 일부 코드에 의해 발생합니다. 배열의 실제 길이를 확인하면됩니다. 이 예외를 더 높게 처리하여이를 ‘수정’하고 싶지는 않지만 대신 첫 번째 인스턴스에서 발생하지 않도록함으로써 대부분의 경우 배열 길이를 확인하여 쉽게 수행 할 수 있습니다.

이것을 넣는 또 다른 방법 IndexOutOfRangeException은 단순히 파일럿 (프로그래머) 오류가 아니라 입력 또는 프로그램 상태 BUT에 대한 진정한 제어 부족으로 인해 다른 예외가 발생할 수 있다는 것입니다.


답변