컬렉션이 null 일 때 .NET foreach 루프가 NullRefException을 발생시키는 이유는 무엇입니까?


231

그래서 나는 종종이 상황을 겪습니다 ... 여기서 Do.Something(...)null 컬렉션을 반환합니다 :

int[] returnArray = Do.Something(...);

그런 다음이 컬렉션을 다음과 같이 사용하려고합니다.

foreach (int i in returnArray)
{
    // do some more stuff
}

궁금합니다 .foreach 루프가 null 컬렉션에서 작동하지 않는 이유는 무엇입니까? 0 개의 반복이 null 컬렉션으로 실행되는 것이 논리적 인 것 같습니다 ... 대신에 NullReferenceException. 왜 이것이 될 수 있는지 아는 사람이 있습니까?

반환하는 내용이 확실하지 않은 API로 작업하면서 짜증나는 일 이니 if (someCollection != null)어디에서나 끝납니다 ...

편집 :foreach 사용법 을 설명해 주셔서 감사합니다 .GetEnumerator 얻을 열거자가 없으면 foreach가 실패합니다. 열거 형을 잡기 전에 언어 / 런타임이 null 검사를 수행 할 수없는 이유를 묻고있는 것 같습니다. 그 행동은 여전히 ​​잘 정의되어있는 것 같습니다.


1
배열을 컬렉션으로 호출하는 데 문제가 있습니다. 하지만 어쩌면 나는 단지 오래된 학교입니다.
Robaticus

예, 동의합니다 ...이 코드베이스의 많은 메소드가 왜 배열
x_x를

4
같은 이유로 추론 C #의 모든 명령문이 null값이 주어지면 no-ops 가되도록 잘 정의되어 있다고 가정 합니다. foreach루프 또는 다른 문장에 대해서도 이것을 제안하고 있습니까?
Ken

7
@ Ken ... foreach 루프 만 생각하고 있습니다. 컬렉션이 비어 있거나 존재하지 않으면 아무 일도 일어나지 않을 것이라는 점이 프로그래머에게는 분명해 보입니다.
Polaris878

답변:


251

짧은 대답은 "컴파일러 디자이너가 설계 한 방식이기 때문입니다." 그러나 실제로는 컬렉션 객체가 null이므로 컴파일러가 열거자를 통해 컬렉션을 반복 할 수있는 방법이 없습니다.

실제로 이와 같은 작업을 수행 해야하는 경우 null 병합 연산자를 사용해보십시오.

int[] array = null;

foreach (int i in array ?? Enumerable.Empty<int>())
{
   System.Console.WriteLine(string.Format("{0}", i));
}

3
나의 무지를 용서해주십시오. 그러나 이것은 효율적입니까? 각 반복을 비교하지 않습니까?
user919426

20
나는 그렇게 믿지 않습니다. 생성 된 IL을 살펴보면 루프는 is null 비교 이후입니다.
Robaticus

10
홀리 네크로 ... 때로는 효율성에 영향을 미치는지 알아 내기 위해 컴파일러가 무엇을하는지 확인하기 위해 IL을 살펴 봐야합니다. User919426은 각 반복에 대한 점검을 수행했는지 여부를 묻습니다. 일부 사람들에게는 그 대답이 명백 할 수도 있지만, 모든 사람들에게 명백하지는 않으며, IL을 살펴보면 컴파일러가 무엇을하고 있는지 알려주고 나중에 사람들이 자신을 위해 낚시를 할 수 있다는 힌트를 제공합니다.
Robaticus

2
@Robaticus (이후의 이유는 무엇 일지라도) IL은 그 이유 때문에 사양이 그렇게 말합니다. 구문 설탕 (일명 foreach)의 확장은 "in"의 오른쪽에있는 표현을 평가 GetEnumerator하고 결과를 불러오는 것 입니다.
Rune FS

2
@RuneFS-정확히. 사양을 이해하거나 IL을 살펴보면 "이유"를 파악할 수 있습니다. 또는 두 개의 다른 C # 접근 방식이 동일한 IL로 귀결되는지 여부를 평가합니다. 그것은 본질적으로 위의 Shimmy에 대한 나의 요점이었습니다.
Robaticus 2016 년

148

foreach루프는 호출 GetEnumerator방법.
컬렉션이 null이면이 메서드 호출은을 발생 NullReferenceException시킵니다.

null컬렉션 을 반환하는 것은 좋지 않습니다 . 대신 메소드가 빈 컬렉션을 반환해야합니다.


7
나는 빈 컬렉션을 항상 반환해야한다는 것에 동의하지만 ...이 방법을 쓰지 않았습니다 :)
Polaris878

19
@Polaris, 구조에 null 통합 연산자! int[] returnArray = Do.Something() ?? new int[] {};
JSB ձոգչ

2
또는 : ... ?? new int[0].
Ken

3
+1 null 대신 빈 컬렉션을 반환하는 팁과 같습니다. 감사.
Galilyou

1
나는 나쁜 습관에 동의하지 않는다. ⇒ 함수가 실패하면 빈 컬렉션을 반환 할 수있다. 생성자에 대한 호출, 메모리 할당, 실행되는 많은 코드 일 것이다. «null»을 반환 할 수 있습니다. → 분명히 리턴 할 코드 만 있고 확인해야 할 매우 짧은 코드는«null»입니다. 성능 일뿐입니다.
Hi-Angel

47

빈 컬렉션과 컬렉션에 대한 null 참조에는 큰 차이가 있습니다.

foreach내부적으로 를 사용 하면 IEnumerable의 GetEnumerator () 메서드 가 호출 됩니다. 참조가 null 인 경우,이 예외가 발생합니다.

그러나 빈 IEnumerable또는 을 갖는 것은 완벽하게 유효합니다 IEnumerable<T>. 이 경우, foreach는 컬렉션이 비어 있기 때문에 어떤 것에 대해서도 "반복"하지는 않지만 완벽하게 유효한 시나리오이기 때문에 던지지도 않습니다.


편집하다:

개인적 으로이 문제를 해결 해야하는 경우 확장 방법을 권장합니다.

public static IEnumerable<T> AsNotNull<T>(this IEnumerable<T> original)
{
     return original ?? Enumerable.Empty<T>();
}

그런 다음 전화하십시오.

foreach (int i in returnArray.AsNotNull())
{
    // do some more stuff
}

3
예, 그러나 열거자를 얻기 전에 null 검사를 수행하지 않는 이유는 무엇입니까?
Polaris878

12
@ Polaris878 : null 컬렉션과 함께 사용되지 않았기 때문입니다. null 참조와 빈 컬렉션은 별도로 처리해야하므로 이것은 IMO입니다. 당신이이 문제를 해결하려면 ... 할께요 편집 한 다른 옵션을 보여 .. 방법이 있습니다
리드 Copsey

1
@ Polaris878 : "왜 열거자를 받기 전에 런타임에서 null 검사를 수행해야합니까?"
리드 콥시

"왜 안돼?"라고 묻고있는 것 같습니다. lol 그것은 행동이 여전히 잘 정의 된 것처럼 보인다
Polaris878

2
@ Polola878 : 내 생각에, 컬렉션에 대해 null을 반환하는 것은 오류라고 생각합니다. 현재 상황에서 런타임은이 경우 의미있는 예외를 제공하지만이 동작이 마음에 들지 않으면 해결하기 쉽습니다 (예 : 위). 컴파일러가 이것을 숨기면 런타임에 오류 검사를 잃게되지만 "해제"할 방법은 없습니다 ...
Reed Copsey

12

그것은 오래 전에 대답하고 있지만 null 포인터 예외를 피하기 위해 다음과 같은 방법 으로이 작업을 시도했지만 C # null check operator?를 사용하는 사람에게 유용 할 수 있습니다.

     //fragments is a list which can be null
     fragments?.ForEach((obj) =>
        {
            //do something with obj
        });

@kjbartel은 1 년 넘게이 문제를 해결했습니다 ( " stackoverflow.com/a/32134295/401246 "). )가되지 않기 때문에 이것은 가장 좋은 솔루션입니다 : (심지어하지 않을 때의 성능 저하가) 참여 null의 LCD에 전체 루프를 일반화)를 Enumerable사용하는 것으로 ( ??) 것, b는) 모든 프로젝트에 확장 메서드를 추가 할 필요, 그리고 c) null IEnumerables (Pffft! Puh-LEAZE! SMH.)를 피해야합니다.
Tom

10

이 문제를 해결하기위한 또 다른 확장 방법 :

public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
{
    if(items == null) return;
    foreach (var item in items) action(item);
}

여러 가지 방법으로 소비하십시오.

(1) T다음 을 받아들이는 방법

returnArray.ForEach(Console.WriteLine);

(2) 식 :

returnArray.ForEach(i => UpdateStatus(string.Format("{0}% complete", i)));

(3) 여러 줄 익명 방법

int toCompare = 10;
returnArray.ForEach(i =>
{
    var thisInt = i;
    var next = i++;
    if(next > 10) Console.WriteLine("Match: {0}", i);
});

세 번째 예에서 닫는 괄호가 없습니다. 그렇지 않으면, 재미있는 방법으로 더 확장 될 수있는 아름다운 코드 (루프, 반전, 도약 등). 공유해 주셔서 감사합니다.
Lara

이러한 훌륭한 코드를 주셔서 감사합니다,하지만 난 그 배열을 인쇄하지만 당신은 매개 변수로 console.writeline을 통과 왜 첫 번째 방법을 이해하지 못했다 elements.but을 didnt 이해
아제 싱

@AjaySingh Console.WriteLine는 하나의 인수 ( Action<T>) 를 취하는 메소드의 예일뿐입니다 . 항목 1, 2 및 3은 .ForEach확장 메소드에 함수를 전달하는 예를 보여줍니다 .
Jay

@ kjbartel의 대답 ( "에서 stackoverflow.com/a/32134295/401246은 그렇지 않기 때문에"최선의 해결책은있다 : A)는 (도하지 않을 때의 성능 저하를 수반 null의 LCD에 전체 루프를 일반화) Enumerable사용으로 ( ??것 ), b) 모든 프로젝트에 확장 방법을 추가해야합니다. 또는 c) null IEnumerables (Pffft! Puh-LEAZE! SMH.)로 시작하지 않아야합니다 (cuz, nullN / A를 의미하지만 빈 목록은 적용되지만 적용됨). 현재 비어 있습니다 . 즉, 예를 들어 Empl.은 비 판매에 대해서는 N / A이거나 판매에 대해서는 비어있을 수 있습니다).
Tom

5

확장 방법을 작성하면 도움이됩니다.

public static class Extensions
{
   public static void ForEachWithNull<T>(this IEnumerable<T> source, Action<T> action)
   {
      if(source == null)
      {
         return;
      }

      foreach(var item in source)
      {
         action(item);
      }
   }
}

5

null 컬렉션은 빈 컬렉션과 같지 않기 때문입니다. 빈 컬렉션은 요소가없는 컬렉션 개체입니다. null 컬렉션은 존재하지 않는 개체입니다.

다음은 시도해 볼만한 사항입니다. 두 가지 컬렉션을 모두 선언하십시오. 비어 있도록 하나를 정상적으로 초기화하고 다른 하나에 값을 할당하십시오 null. 그런 다음 두 컬렉션 모두에 개체를 추가하고 어떻게되는지 확인하십시오.


3

의 잘못입니다 Do.Something(). 여기서 가장 좋은 방법은 null 대신 0 크기의 배열을 반환하는 것입니다.


2

무대 뒤에서 foreach인수는 다음과 동등한 열거자를 얻습니다.

using (IEnumerator<int> enumerator = returnArray.getEnumerator()) {
    while (enumerator.MoveNext()) {
        int i = enumerator.Current;
        // do some more stuff
    }
}

2
그래서? 왜 null인지 먼저 확인하고 루프를 건너 뛸 수없는 이유는 무엇입니까? AKA, 정확히 확장 방법에 무엇이 표시됩니까? 문제는 null 인 경우 루프를 건너 뛰거나 예외를 throw하는 것이 더 낫습니까? 건너 뛰는 것이 낫다고 생각합니다! null의 용기는 생략하는 대신 루프가 뭔가를 의미하기 때문에 이상 반복되는 것을 의미하는 것으로 보인다 경우 컨테이너가 null입니다.
AbstractDissonance

@AbstractDissonance null멤버에 액세스 할 때 와 같이 모든 참조 에서 동일하게 논쟁 할 수 있습니다. 일반적으로 이것은 오류이며, 그렇지 않은 경우 다른 사용자가 답변으로 제공 한 확장 메서드를 사용하여이를 처리하기에 간단합니다.
Lucero

1
나는 그렇게 생각하지 않습니다. foreach는 컬렉션에서 작동하도록 만들어졌으며 null 개체를 직접 참조하는 것과 다릅니다. 하나는 논쟁의 여지가 있지만 세계의 모든 코드를 분석하면 대부분의 foreach 루프가 컬렉션이 "null"일 때 루프를 우회하기 위해 루프 앞에 어떤 종류의 null 검사가 있어야합니다. 따라서 공란으로 취급). 누구도 원하는대로 null 컬렉션을 반복하는 것을 고려하지 않으며 컬렉션이 null 인 경우 루프를 무시하기 만합니다. 오히려 foreach? (var x in C)를 사용할 수 있습니다.
AbstractDissonance

내가 주로하려고하는 요점은 코드를 작성하는 데 아무런 이유없이 매번 확인해야하기 때문에 약간의 쓰레기가 생성된다는 것입니다. 확장 기능은 물론 작동하지만 언어 기능을 추가하여 많은 문제없이 이러한 일을 피할 수 있습니다. (주로 나는 현재 방법이 숨겨진 버그를 생성한다고 생각합니다. 프로그래머는 검사를 잊어 버릴 수 있기 때문에 예외가 있습니다 ... 루프 전에 검사가 다른 곳에서 발생할 것으로 예상하거나 사전 초기화되었다고 생각하기 때문에 그러나 어느 쪽이든, 행동은 비어있는 것과 동일합니다.
AbstractDissonance

@AbstractDissonance 글쎄, 적절한 정적 분석을 통해 널이있을 수있는 곳과 그렇지 않은 곳을 알 수 있습니다. 예상하지 못한 곳에서 널을 얻는다면 IMHO 문제를 자동으로 무시하는 대신 실패하는 것이 좋습니다 ( 빠른 실패 의 정신으로 ). 따라서 이것이 올바른 행동이라고 생각합니다.
Lucero

1

예외가 발생하는 이유에 대한 설명은 여기에 제공된 답변으로 매우 명확하다고 생각합니다. 나는 일반적으로 theese 컬렉션으로 작업하는 방식을 보완하고 싶습니다. 때로는 컬렉션을 한 번 이상 사용하고 매번 null인지 테스트해야하기 때문입니다. 이를 피하기 위해 다음을 수행합니다.

    var returnArray = DoSomething() ?? Enumerable.Empty<int>();

    foreach (int i in returnArray)
    {
        // do some more stuff
    }

이런 식으로 예외를 두려워하지 않고 원하는만큼 컬렉션을 사용할 수 있으며 과도한 조건문으로 코드를 오염시키지 않습니다.

null check 연산자를 사용하는 ?.것도 좋은 방법입니다. 그러나 배열의 경우 (문제의 예와 같이) 다음과 같이 전에 List로 변환해야합니다.

    int[] returnArray = DoSomething();

    returnArray?.ToList().ForEach((i) =>
    {
        // do some more stuff
    });

2
ForEach메소드에 액세스 할 수 있도록 목록으로 변환 하는 것은 코드베이스에서 싫어하는 것 중 하나입니다.
huysentruitw

동의합니다 ... 가능한 한 많이 피합니다. :(
Alielson Piffer

-2
SPListItem item;
DataRow dr = datatable.NewRow();

dr["ID"] = (!Object.Equals(item["ID"], null)) ? item["ID"].ToString() : string.Empty;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.