IEnumerable의 다중 열거 가능성에 대한 경고 처리 || !objects.Any())

내 코드에서 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();
    // ...
 }

그러나 이것은 어색하다 .

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



답변

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

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

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

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

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

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

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


답변

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

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반환 할 목록에 추가하는 대신 항목을 가져올 때 항목을 가져옵니다.


답변

사용 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>항상 호환 되는 모든 위치에 수동으로 추가해야합니다 .)

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


답변

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일반적이지 않거나 최소한 참조 유형으로 제한되어 있다고 가정합니다 .


답변

이 상황에서는 일반적으로 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 오버로드가 처리하도록 할 수 있습니다.


답변

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

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

답변

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

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

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

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