[C#] LINQ로 목록이 비어 있는지 확인

목록이 비어 있는지 확인하는 “가장 좋은”(속도와 가독성을 모두 고려) 방법은 무엇입니까? 목록이 유형 IEnumerable<T>이고 Count 속성이없는 경우에도 마찬가지입니다.

지금 나는 이것 사이에 던지고있다 :

if (myList.Count() == 0) { ... }

이:

if (!myList.Any()) { ... }

내 생각에 두 번째 옵션은 첫 번째 항목을 보자 마자 결과를 반환 할 것이기 때문에 더 빠르지 만 두 번째 옵션 (IEnumerable의 경우)은 개수를 반환하기 위해 모든 항목을 방문해야합니다.

즉, 두 번째 옵션이 읽기 쉬운 것처럼 보입니까? 어느 걸 더 선호하십니까? 아니면 빈 목록을 테스트하는 더 좋은 방법을 생각할 수 있습니까?

@lassevk의 응답을 편집 하는 것은 가능한 경우 캐시 된 카운트를 사용하기위한 약간의 런타임 검사와 결합 된 가장 논리적 인 것 같습니다.

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list is ICollection<T>) return ((ICollection<T>)list).Count == 0;

    return !list.Any();
}



답변

다음과 같이 할 수 있습니다.

public static Boolean IsEmpty<T>(this IEnumerable<T> source)
{
    if (source == null)
        return true; // or throw an exception
    return !source.Any();
}

편집 : 기본 소스에 실제로 빠른 Count 속성이 있으면 단순히 .Count 메서드를 사용하는 것이 빠릅니다. 위의 유효한 최적화는 몇 가지 기본 유형을 감지하고 .Any () 접근 방식 대신 .Count 속성을 사용하지만 보장 할 수없는 경우 .Any ()로 대체하는 것입니다.


답변

나는 당신이 정착 한 것처럼 보이는 코드에 하나의 작은 추가를 할 것입니다. ICollection이것은 쓸모없는 제네릭 클래스 (즉, Queue<T>Stack<T>) 에서도 구현되기 때문에 또한 확인하십시오 . 나는 또한 더 관용적이고 더 빠른 것으로 나타났기 때문에 as대신 사용할 것입니다 .is

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list == null)
    {
        throw new ArgumentNullException("list");
    }

    var genericCollection = list as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == 0;
    }

    var nonGenericCollection = list as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == 0;
    }

    return !list.Any();
}


답변

LINQ 자체는 어떻게 든 Count () 메서드에 대해 심각한 최적화를 수행해야합니다.

이것이 당신을 놀라게합니까? IList구현의 경우 Count단순히 요소 수를 직접 읽고 메서드 Any를 쿼리하고 IEnumerable.GetEnumerator인스턴스를 만들고 MoveNext적어도 한 번 호출 해야한다고 생각합니다 .

/ @Matt 편집 :

IEnumerable의 Count () 확장 메서드가 다음과 같은 작업을 수행한다고 가정 할 수 있습니다.

예, 물론입니다. 이것이 내가 의미하는 바입니다. 실제로 ICollection대신 사용 IList하지만 결과는 동일합니다.


답변

방금 간단한 테스트를 작성했습니다.

 IEnumerable<Object> myList = new List<Object>();

 Stopwatch watch = new Stopwatch();

 int x;

 watch.Start();
 for (var i = 0; i <= 1000000; i++)
 {
    if (myList.Count() == 0) x = i;
 }
 watch.Stop();

 Stopwatch watch2 = new Stopwatch();

 watch2.Start();
 for (var i = 0; i <= 1000000; i++)
 {
     if (!myList.Any()) x = i;
 }
 watch2.Stop();

 Console.WriteLine("myList.Count() = " + watch.ElapsedMilliseconds.ToString());
 Console.WriteLine("myList.Any() = " + watch2.ElapsedMilliseconds.ToString());
 Console.ReadLine();

두 번째는 거의 세 배 느립니다. 🙂

스택이나 배열 또는 다른 시나리오로 스톱워치 테스트를 다시 시도하면 실제로 보이는 목록 유형에 따라 다릅니다. Count가 더 느리다는 것을 증명하기 때문입니다.

따라서 사용중인 목록 유형에 따라 다릅니다.

(단지 지적하자면, 저는 목록에 2000 개 이상의 개체를 넣었고 다른 유형과는 반대로 개수가 더 빨랐습니다.)


답변

List.CountMicrosoft 문서에 따르면 O (1)입니다.
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

그래서 그냥 사용 List.Count == 0이 쿼리보다 훨씬 빠릅니다

이것은 목록에서 무언가가 추가되거나 제거 될 때마다 업데이트되는 Count라는 데이터 멤버가 있기 때문입니다. 따라서 호출 List.Count할 때 모든 요소를 ​​반복 할 필요가 없으며 데이터 멤버 만 반환합니다.


답변

두 번째 옵션은 여러 항목이있는 경우 훨씬 더 빠릅니다.

  • Any() 항목이 1 개 발견되는 즉시 반환됩니다.
  • Count() 전체 목록을 계속 검토해야합니다.

예를 들어 열거 형에 1000 개의 항목이 있다고 가정합니다.

  • Any() 첫 번째 것을 확인한 다음 true를 반환합니다.
  • Count() 전체 열거를 순회 한 후 1000을 반환합니다.

술어 재정의 중 하나를 사용하는 경우 잠재적으로 더 나빠질 수 있습니다. Count ()는 여전히 일치하는 항목이 하나만 있더라도 모든 단일 항목을 확인해야합니다.

Any one을 사용하는 데 익숙해집니다. 의미가 있고 읽을 수 있습니다.

한 가지주의 사항-IEnumerable이 아닌 List가있는 경우 해당 목록의 Count 속성을 사용하십시오.


답변

@Konrad 나를 놀라게 한 것은 내 테스트에서 목록을 수락하는 메서드로 전달 IEnumerable<T>하므로 런타임이 .NET 용 Count () 확장 메서드를 호출하여이를 최적화 할 수 없다는 것입니다 IList<T>.

IEnumerable의 Count () 확장 메서드가 다음과 같은 작업을 수행한다고 가정 할 수 있습니다.

public static int Count<T>(this IEnumerable<T> list)
{
    if (list is IList<T>) return ((IList<T>)list).Count;

    int i = 0;
    foreach (var t in list) i++;
    return i;
}

… 즉, IList<T>.

/ EDIT @Konrad +1 mate-당신이 옳다고 생각합니다 ICollection<T>.