IEnumerable의 다중 열거 가능성에 대한 경고 처리


358

내 코드에서 IEnumerable<>여러 번 사용해야 하므로 "가능한 다중 열거 가능"의 Resharper 오류가 발생 IEnumerable합니다.

샘플 코드 :

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null || !objects.Any())
        throw new ArgumentException();

    var firstObject = objects.First();
    var list = DoSomeThing(firstObject);        
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}
  • objects매개 변수를 변경 한 List다음 가능한 다중 열거를 피할 수 있지만 처리 할 수있는 가장 높은 객체를 얻지 못합니다.
  • 내가 할 수있는 또 다른 점은 변환하는 것입니다 IEnumerableList방법의 시작 부분에 :

 public List<object> Foo(IEnumerable<object> objects)
 {
    var objectList = objects.ToList();
    // ...
 }

그러나 이것은 어색하다 .

이 시나리오에서 무엇을 하시겠습니까?

답변:


470

복용의 문제 IEnumerable를 매개 변수로는 발신자를 알려줍니다 "나는이를 열거 할"것입니다. 몇 번이나 열거하고 싶은지 알려주지 않습니다.

objects 매개 변수를 List로 변경 한 다음 가능한 다중 열거를 피할 수 있지만 처리 할 수있는 가장 높은 개체를 얻지 못합니다 .

가장 높은 목표를 취하는 목표는 고귀하지만 너무 많은 가정의 여지가 남아 있습니다. 누군가가 LINQ to SQL 쿼리를이 메소드에 전달하고 싶을 때만 두 번만 열거하면됩니다 (매번 다른 결과를 얻을 수 있습니까?)

여기서 의미 론적 누락은 메소드의 세부 사항을 읽는 데 시간이 걸리지 않는 호출자가 한 번만 반복한다고 가정 할 수 있으므로 값 비싼 오브젝트를 전달한다는 것입니다. 메소드 서명은 어느 쪽도 나타내지 않습니다.

메소드 서명을 IList/ 로 변경하면 ICollection최소한 발신자에게 기대하는 바를 명확하게하고 값 비싼 실수를 피할 수 있습니다.

그렇지 않으면 메소드를보고있는 대부분의 개발자가 한 번만 반복한다고 가정 할 수 있습니다. 복용 IEnumerable이 매우 중요한 .ToList()경우 분석법 시작시 수행을 고려해야 합니다.

.NET에는 IEnumerable + Count + Indexer 인 인터페이스가 없기 때문에 Add / Remove 등의 방법이 없습니다.이 문제를 해결할 것으로 생각됩니다.


31
ReadOnlyCollection <T>이 원하는 인터페이스 요구 사항을 충족합니까? msdn.microsoft.com/ko-kr/library/ms132474.aspx
Dan은 Firelight

73
@ DanNeely 나는 여러 번 열거 될 IReadOnlyCollection(T)것이라는 아이디어를 전달하기위한 최상의 인터페이스로 .net 4.5의 새로운 기능을 제안 할 것 IEnumerable(T)입니다. 이 답변에서 알 수 있듯이 IEnumerable(T)그 자체는 너무 일반적이므로 부작용없이 다시 열거 할 수없는 재설정 할 수없는 내용을 나타낼 수도 있습니다. 그러나 IReadOnlyCollection(T)반복성을 의미합니다.
binki

1
.ToList()메소드 시작시 를 수행하는 경우 먼저 매개 변수가 널이 아닌지 확인해야합니다. 즉, ArgumentException을 발생시키는 두 곳에서 매개 변수가 null이고 항목이없는 경우가 발생합니다.
comecme

vs 의 의미에 대해이 기사 를 읽은 다음 매개 변수로 사용 하는 방법과 어떤 경우 에 대한 질문 을 게시 했습니다 . 나는 결국 결론 을 따를 논리 라고 생각합니다 . 열거가 필요한 곳에서 사용해야하며 반드시 여러 열거가있을 수있는 곳은 아닙니다 . IReadOnlyCollection<T>IEnumerable<T>IReadOnlyCollection<T>
Neo

32

데이터를 항상 반복 할 수 있다면 걱정하지 않아도됩니다. 그러나 언 롤링 할 수도 있습니다. 이는 들어오는 데이터가 클 수있는 경우에 특히 유용합니다 (예 : 디스크 / 네트워크에서 읽기).

if(objects == null) throw new ArgumentException();
using(var iter = objects.GetEnumerator()) {
    if(!iter.MoveNext()) throw new ArgumentException();

    var firstObject = iter.Current;
    var list = DoSomeThing(firstObject);  

    while(iter.MoveNext()) {
        list.Add(DoSomeThingElse(iter.Current));
    }
    return list;
}

참고 DoSomethingElse의 의미를 약간 변경했지만 주로 롤링되지 않은 사용법을 보여주기위한 것입니다. 예를 들어 반복자를 다시 감쌀 수 있습니다. 반복자 블록으로 만들 수도 있습니다. 그런 다음 list- 이 없으며 yield return반환 할 목록에 추가하는 대신 항목을 가져올 때 항목을 가져옵니다.


4
+1 글쎄요. 아주 좋은 코드이지만 코드의 가독성과 가독성이 느슨합니다.
gdoron이 Monica를 지원합니다.

5
@gdoron 모든 것이 예쁘지는 않습니다 .p 위의 내용을 읽을 수는 없습니다. 특히 반복자 블록으로 만들어지면 매우 귀여운 IMO입니다.
Marc Gravell

"var objectList = objects.ToList ();"라고 쓸 수 있습니다. 모든 열거 자 처리를 저장하십시오.
gdoron은 Monica를 지원합니다.

10
@gdoron 당신이 명시 적으로 그렇게하고 싶지 않다고 명시 적으로 말했다; p 또한, ToList () 접근법은 데이터가 매우 클 경우 (잠재적으로, 무한-시퀀스가 유한 할 필요는 없지만 적합 할 수 있다면) 여전히 반복됩니다). 궁극적으로, 나는 단지 옵션을 제시하고 있습니다. 그것이 정당한지 알 수있는 전체 내용을 아는 사람 만이 있습니다. 데이터가 반복 가능한 경우 다른 유효한 옵션은 다음과 같습니다. 아무것도 변경하지 마십시오! 대신 R #을 무시하십시오 ... 모두 컨텍스트에 따라 다릅니다.
Marc Gravell

1
나는 Marc의 해결책이 꽤 좋다고 생각한다. 1. '목록'이 초기화되는 방법이 명확하고 목록이 어떻게 반복되는지가 명확하며 목록의 어떤 멤버가 작동하는지가 매우 명확합니다.
Keith Hoffman

8

사용 IReadOnlyCollection<T>또는 IReadOnlyList<T>대신 메서드 서명에 IEnumerable<T>, 당신이 반복하는 전에 수를 확인해야 할 수도 있습니다, 또는 다른 이유로 여러 번 반복하는 것을 명시 적으로 만드는 장점이있다.

그러나 인터페이스를 사용하기 위해 코드를 리팩토링하려고 할 때 (예를 들어 동적 프록시에 대한 테스트 및 친숙성을 높이기 위해) 문제를 일으킬 수있는 큰 단점이 있습니다. 요점은 IList<T>에서 상속되지 않으며IReadOnlyList<T> 다른 컬렉션과 해당 읽기 전용 인터페이스에 대해서도 마찬가지입니다. (.NET 4.5은 이전 버전의 ABI 호환성을 유지하고 싶었다. 때문에 즉,이다 하지만 그들은 심지어 .NET의 핵심에서 변화에 대한 기회를하지 않았다. )

IList<T>, 프로그램의 일부에서 가져 와서을 기대하는 다른 부분으로 전달하려는 IReadOnlyList<T>경우 할 수 없습니다! 당신은 그러나를 전달할 수 있습니다 IList<T>int로서IEnumerable<T> .

결국 IEnumerable<T>모든 컬렉션 인터페이스를 포함한 모든 .NET 컬렉션에서 지원하는 유일한 읽기 전용 인터페이스입니다. 당신이 어떤 건축 선택에서 자신을 가두 었다는 것을 깨달았을 때 다른 대안이 다시 당신을 물 것입니다. 따라서 읽기 전용 컬렉션을 원한다는 것을 표현하기 위해 함수 서명에 사용하는 것이 올바른 유형이라고 생각합니다.

( IReadOnlyList<T> ToReadOnly<T>(this IList<T> list)기본 유형이 두 인터페이스를 모두 지원하는 경우 단순 캐스트를 사용 하는 확장 메소드를 항상 작성할 수 있지만 리팩토링 할 때 IEnumerable<T>항상 호환 되는 모든 위치에 수동으로 추가해야합니다 .)

우연히 여러 가지 열거 형이 재앙이 될 수있는 데이터베이스가 많은 코드를 작성하는 경우에는 이것이 절대적인 것은 아닙니다. 다른 절충안을 선호 할 수도 있습니다.


2
+1 .NET 플랫폼에서 제공하는 새로운 기능 (예 : IReadOnlyCollection <T>)으로이 문제를 해결하는 새로운 방법에 대한 문서를 추가하고 구한 게시물
Kevin Avignon

4

Marc Gravell의 답변보다 여러 열거를 실제로 방지하는 것이 목표이지만 동일한 의미를 유지하면 중복 AnyFirst호출을 간단하게 제거하고 다음을 수행 할 수 있습니다.

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null)
        throw new ArgumentNullException("objects");

    var first = objects.FirstOrDefault();

    if (first == null)
        throw new ArgumentException(
            "Empty enumerable not supported.", 
            "objects");

    var list = DoSomeThing(first);  

    var secondList = DoSomeThingElse(objects);

    list.AddRange(secondList);

    return list;
}

이것은 IEnumerable일반적이지 않거나 최소한 참조 유형으로 제한되어 있다고 가정합니다 .


2
objects여전히 DoSomethingElse로 전달되어 목록을 반환하므로 여전히 두 번 열거 할 가능성이 여전히 있습니다. 컬렉션이 LINQ to SQL 쿼리 인 경우 DB를 두 번 누르십시오.
Paul Stovell

1
@PaulStovell, 이것을 읽으십시오 : "목표가 정말로 Marc Gravell의 답변보다 여러 열거를 막는
것이라면

빈 목록에 대한 예외를 던지는 것에 대한 다른 stackoverflow 게시물에서 제안되었으며 여기에서 제안합니다. 왜 빈 목록에 예외를 던지겠습니까? 빈 목록을 얻고 빈 목록을 제공하십시오. 패러다임으로서, 그것은 매우 간단합니다. 발신자가 빈 목록을 전달하는지 모른다면 문제가있는 것입니다.
Keith Hoffman

@Keith Hoffman, 샘플 코드는 OP가 게시 한 코드와 동일한 동작을 유지하며이 First경우 빈 목록에 대한 예외가 발생합니다. 나는 빈 목록에 예외를 던지거나 항상 빈 목록에 예외를 던질 수 있다고 생각하지 않습니다. 그것은 다릅니다 ...
João Angelo

충분히 주아. 게시물에 대한 비판보다 생각을위한 음식이 더 많습니다.
Keith Hoffman

3

이 상황에서는 일반적으로 IEnumerable 및 IList로 메서드를 오버로드합니다.

public static IEnumerable<T> Method<T>( this IList<T> source ){... }

public static IEnumerable<T> Method<T>( this IEnumerable<T> source )
{
    /*input checks on source parameter here*/
    return Method( source.ToList() );
}

IEnumerable을 호출하면 .ToList ()를 수행하는 메서드의 요약 설명에서 설명합니다.

프로그래머는 여러 작업이 연결되어있는 경우 더 높은 수준에서 .ToList ()를 선택한 다음 IList 오버로드를 호출하거나 IEnumerable 오버로드가 처리하도록 할 수 있습니다.


20
이것은 끔찍하다.
Mike Chamberlain

1
@MikeChamberlain 그래, 정말 끔찍하고 동시에 깨끗합니다. 불행히도 일부 무거운 시나리오에는이 방법이 필요합니다.
Mauro Sampietro

1
그러나 배열을 IEnumerable parameratized 메소드에 전달할 때마다 배열을 다시 작성합니다. 이 점에서 @ MikeChamberlain에 동의합니다. 이것은 끔찍한 제안입니다.
Krythic

2
@Krythic 목록을 다시 만들 필요가 없으면 다른 곳에서 ToList를 수행하고 IList 오버로드를 사용합니다. 이것이이 솔루션의 요점입니다.
Mauro Sampietro

0

첫 번째 요소 만 확인해야하는 경우 전체 컬렉션을 반복하지 않고 살펴볼 수 있습니다.

public List<object> Foo(IEnumerable<object> objects)
{
    object firstObject;
    if (objects == null || !TryPeek(ref objects, out firstObject))
        throw new ArgumentException();

    var list = DoSomeThing(firstObject);
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}

public static bool TryPeek<T>(ref IEnumerable<T> source, out T first)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    IEnumerator<T> enumerator = source.GetEnumerator();
    if (!enumerator.MoveNext())
    {
        first = default(T);
        source = Enumerable.Empty<T>();
        return false;
    }

    first = enumerator.Current;
    T firstElement = first;
    source = Iterate();
    return true;

    IEnumerable<T> Iterate()
    {
        yield return firstElement;
        using (enumerator)
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current;
            }
        }
    }
}

-3

우선, 그 경고가 항상 그렇게 많은 것을 의미하지는 않습니다. 성능 병목이 아닌지 확인한 후 일반적으로 사용 중지했습니다. 그것은 단지 IEnumerable두 번 평가 된다는 것을 의미하며 , evaluation자체 시간이 오래 걸리지 않으면 일반적으로 문제가되지 않습니다 . 시간이 오래 걸리더라도이 경우 처음으로 하나의 요소 만 사용하십시오.

이 시나리오에서는 강력한 linq 확장 방법을 훨씬 더 활용할 수도 있습니다.

var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();

IEnumerable이 경우 번거롭지 만 한 번만 평가할 수 있지만 프로파일을 먼저 작성하여 실제로 문제가 있는지 확인하십시오.


15
나는 IQueryable로 많은 일을하고 그런 종류의 경고를 끄는 것은 매우 위험하다.
gdoron은 Monica를 지원합니다.

5
내 책에서는 두 번 평가하는 것이 좋지 않습니다. 메소드가 IEnumerable을 사용하는 경우 LINQ to SQL 쿼리를 전달하여 여러 DB 호출을 유발할 수 있습니다. 메소드 서명은 두 번 열거 될 수 있음을 나타내지 않으므로 서명을 변경하거나 (IList / ICollection 허용) 한 번만 반복해야합니다.
Paul Stovell

4
초기와 망가진 가독성을 최적화하고 나중에 차이 를 만드는 최적화를 수행 할 수있는 가능성 은 내 책에서 훨씬 더 나쁩니다.
vidstige

당신은 데이터베이스 쿼리를 가지고 때 일단 확인 만 평가 것 만들기 이미 차이가 있습니다. 그것은 단지 이론적 인 미래의 이익일뿐만 아니라 현재 측정 가능한 실질적인 이익입니다. 단 한 번만 평가하는 것은 성능에만 도움이 될뿐만 아니라 정확성에도 필요합니다. 누군가가 두 쿼리 평가간에 업데이트 명령을 실행했을 수 있으므로 데이터베이스 쿼리를 두 번 실행하면 다른 결과가 생성 될 수 있습니다.

1
@ hvd 나는 그런 것을 추천하지 않았다. 물론 IEnumerables반복 할 때마다 반복하거나 실제로 반복하는 데 시간이 오래 걸리는 열거 형 을 열거해서는 안됩니다 . Marc Gravells의 답변을 참조하십시오-그는 또한 가장 먼저 "아마도 걱정하지 마십시오"라고 말합니다. 중요한 것은-당신이 이것을 보는 많은 시간 동안 당신은 걱정할 것이 없습니다.
vidstige
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.