반복하지 않고 IEnumerable <T>의 항목을 계산합니까?


317
private IEnumerable<string> Tables
{
    get
    {
        yield return "Foo";
        yield return "Bar";
    }
}

그것들을 반복하고 # n의 # n 처리와 같은 것을 작성한다고 가정 해 봅시다.

주요 반복 전에 반복하지 않고 m의 값을 찾을 수있는 방법이 있습니까?

나는 나 자신을 명확하게했다.

답변:


338

IEnumerable이것을 지원하지 않습니다. 이것은 의도적으로 설계된 동작입니다. IEnumerable지연 평가를 사용하여 요청하기 전에 필요한 요소를 가져옵니다.

당신은 당신이 사용할 수있는 그들을 반복하지 않고 항목의 수를 알고 싶은 경우에 ICollection<T>, 그것은이 Count속성을.


38
인덱서로 목록에 액세스 할 필요가 없으면 IList보다 ICollection을 선호합니다.
Michael Meadows

3
나는 보통 습관적으로 List와 IList를 가져옵니다. 그러나 특히 직접 구현하려는 경우 ICollection이 더 쉽고 Count 속성도 있습니다. 감사!
Mendelt

21
@Shimmy 당신은 요소를 반복하고 계산합니다. 또는 Linq 네임 스페이스에서 Count ()를 호출하여이를 수행합니다.
Mendelt

1
정상적인 상황에서 IEnumerable을 IList로 바꾸는 것만으로 충분합니까?
Teekin

1
@Helgi IEnumerable은 느리게 평가되므로 IList를 사용할 수없는 것들에 사용할 수 있습니다. 예를 들어 Pi의 모든 소수를 열거하는 IEnumerable을 반환하는 함수를 작성할 수 있습니다. 당신이 완전한 결과에 대해 절대로 가르치려고하지 않는 한 그것은 효과가있을 것입니다. Pi를 포함하는 IList를 만들 수 없습니다. 그러나 그것은 모두 꽤 학문적입니다. 대부분의 일반적인 용도에 대해서는 전적으로 동의합니다. Count가 필요한 경우 IList가 필요합니다. :-)
Mendelt

215

System.Linq.Enumerable.Count확장 방법에 대한 IEnumerable<T>다음과 같은 구현이 :

ICollection<T> c = source as ICollection<TSource>;
if (c != null)
    return c.Count;

int result = 0;
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
    while (enumerator.MoveNext())
        result++;
}
return result;

따라서 속성 ICollection<T>이있는 로 캐스팅하려고 시도하고 Count가능하면 사용합니다. 그렇지 않으면 반복됩니다.

따라서 최선의 방법은 객체 에서 Count()확장 방법 을 사용 IEnumerable<T>하는 것입니다.


13
ICollection<T>먼저 캐스팅하려고하는 것이 매우 흥미 롭습니다 .
Oscar Mederos

1
@OscarMederos Enumerable의 대부분의 확장 메서드에는 가능한 한 저렴한 방법을 사용할 수있는 다양한 유형의 시퀀스에 대한 최적화 기능이 있습니다.
Shibumi

1
언급 된 확장명은 .Net 3.5부터 사용 가능하며 MSDN에 문서화되어 있습니다.
Christian

난 당신이 제네릭 형식이 필요하지 않습니다 생각 TIEnumerator<T> enumerator = source.GetEnumerator(): 단순히 당신이 할 수 IEnumerator enumerator = source.GetEnumerator()와 작동합니다!
Jaider

7
@Jaider-그보다 약간 더 복잡합니다. IEnumerable<T>상속 IDisposable수 있습니다 using문은 자동으로 처리합니다. IEnumerable하지 않습니다. 당신이 호출하면 그래서 GetEnumerator어느 쪽이든, 당신은으로 마무리한다var d = e as IDisposable; if (d != null) d.Dispose();
다니엘 Earwicker

87

추가 정보를 추가하기 만하면됩니다.

Count()확장은 항상 반복하지 않습니다. Linq to Sql을 고려하십시오. 여기서 카운트는 데이터베이스로 이동하지만 모든 행을 다시 가져 오는 대신 Sql Count()명령을 실행하고 대신 결과를 반환합니다.

또한 컴파일러 (또는 런타임)는 객체 Count()메소드가있는 경우 객체 메소드를 호출 할 정도로 똑똑 합니다. 그래서 그건 하지 다른 조치가 완전히 무식한 항상 요소를 계산하기 위해 반복, 말하는 것처럼.

프로그래머가 확장 방법을 if( enumerable.Count != 0 )사용하여 검사 하는 경우가 많지만 Any(), if( enumerable.Any() ) linq의 지연 평가에서는 요소가 있으면 단락 될 수 있기 때문에 훨씬 효율적 이기 때문에 확장 방법을 사용하여 검사 하는 경우가 많습니다 . 더 읽기 쉽습니다


2
컬렉션 및 배열과 관련하여 컬렉션을 사용하는 .Count경우 항상 크기를 알고 있으므로 속성 을 사용하십시오 . collection.Count추가 계산이없는 쿼리 는 이미 알려진 카운트를 반환합니다. Array.length내가 아는 한 동일합니다 . 그러나를 .Any()사용하여 소스의 열거자를 가져오고 가능한 using (IEnumerator<TSource> enumerator = source.GetEnumerator())경우 true를 리턴합니다 enumerator.MoveNext(). 컬렉션 : if(collection.Count > 0), 배열 : if(array.length > 0)및 열거 형의 경우 if(collection.Any()).
Nope

1
첫번째 점은 엄격 사실이 아니다 ... SQL에 LINQ는 사용 이 확장 방법 과 동일하지 이것을 . 두 번째를 사용하는 경우, 카운트가 아닌 SQL 함수로 메모리에 수행
AlexFoxGill

1
@AlexFoxGill이 정확합니다. 당신이 명시 적으로 캐스팅하면 IQueryably<T>A를 IEnumerable<T>그것은 SQL 카운트를 발행하지 않습니다. 내가 이것을 쓸 때 Linq to Sql은 새로운 것이 었습니다. Any()열거 형, 컬렉션 및 SQL의 경우 훨씬 효율적이고 읽을 수 있기 때문에 사용하려고 추진하고 있다고 생각 합니다. 답변을 개선해 주셔서 감사합니다.
Robert Paulson

12

내 친구에게 왜 그렇게 할 수 없는지를 보여주는 일련의 블로그 게시물이 있습니다. 그는 각 반복이 다음 소수를 리턴하는 IEnumerable을 리턴하는 함수를 작성 ulong.MaxValue하며, 다음 항목은 요청할 때까지 계산되지 않습니다. 빠르고 대중적인 질문 : 몇 개의 품목이 반환됩니까?

게시물은 다음과 같지만 길다.

  1. Beyond Loops (다른 게시물에 사용 된 초기 EnumerableUtility 클래스 제공)
  2. 반복 적용 (초기 구현)
  3. 미친 확장 방법 : ToLazyList (성능 최적화)

2
나는 정말로 MS가 열거 자에게 자신에 대해 무엇을 할 수 있는지 설명 할 수있는 방법을 정의했으면 좋겠다. "누구나 자신이 유한하다는 것을 알고 있습니까?", "N 개보다 적은 요소로 유한하다는 것을 알고 있습니까?", "너 자신이 무한하다는 것을 알고 있습니까?"와 같은 질문에 대답하는 데 어려움이 없어야합니다. (도움이 없다면) 그들 모두에게 "아니오"라고 대답하십시오. 그러한 질문을하기위한 표준 수단이 있다면, 열거자가 끝없는 시퀀스를 반환하는 것이 훨씬 더 안전 할 것입니다.
supercat

1
... (그들이 그렇게 할 수 있기 때문에) 그리고 코드는 끝없는 시퀀스를 반환한다고 주장하지 않는 열거자가 묶일 것이라고 가정합니다. 이러한 질문을하는 방법 (보일러 플레이트 최소화, 아마도 속성이 EnumerableFeatures객체를 반환하도록 함 )을 포함하는 것은 열거 자에게 어려운 일을 요구할 필요는 없지만 그러한 질문을 할 수있는 것입니다. 항상 같은 순서의 항목을 반환합니다. ","기본 컬렉션을 변경하지 않아야하는 코드에 안전하게 노출시킬 수 있습니까? "등)은 매우 유용합니다.
supercat

꽤 멋지지만 반복 블록과 얼마나 잘 맞을지 확신합니다. 특별한 "수율 옵션"또는 무언가가 필요합니다. 또는 반복자를 사용하여 속성을 사용할 수도 있습니다.
Joel Coehoorn

1
반복자 블록은 다른 특수 선언이없는 경우 IEnumerator 또는 MS 인증 후임자 (존재 여부를 알고있는 GetEnumerator 구현으로 리턴 될 수 있음) 인 경우 리턴 된 시퀀스에 대해 아무것도 모른다고 간단히보고 할 수 있습니다. 추가 정보를 지원해야한다면 C # set yield options은이를 뒷받침 하는 진술이나 유사한 정보를 얻을 수 있습니다. 적절하게 설계된 경우 , 특히 IEnhancedEnumerator"방어 적" ToArray또는 ToList전화를 많이 제거하여 LINQ와 같은 것들을 훨씬 더 유용하게 만들 수 있습니다 .
supercat

... 같은 것들이 Enumerable.Concat자신에 대해 많은 것을 알고있는 큰 컬렉션과 그렇지 않은 작은 컬렉션을 결합하는 데 사용되는 경우 .
supercat

10

IEnumerable은 반복없이 계산할 수 없습니다.

"정상"환경에서는 List <T>와 같이 IEnumerable 또는 IEnumerable <T>을 구현하는 클래스가 List <T> .Count 속성을 반환하여 Count 메서드를 구현할 수 있습니다. 그러나 Count 메서드는 실제로 IEnumerable <T> 또는 IEnumerable 인터페이스에 정의 된 메서드가 아닙니다. (실제로 유일한 것은 GetEnumerator입니다.) 이는 클래스 별 구현을 제공 할 수 없음을 의미합니다.

오히려 Count 메서드는 정적 클래스 Enumerable에 정의 된 확장 메서드입니다. 이는 해당 클래스의 구현에 관계없이 IEnumerable <T> 파생 클래스의 모든 인스턴스에서 호출 될 수 있음을 의미합니다. 그러나 이는 해당 클래스 외부의 단일 위치에서 구현됨을 의미합니다. 물론이 클래스의 내부와 완전히 독립적 인 방식으로 구현되어야 함을 의미합니다. 계산하는 유일한 방법은 반복을 통한 것입니다.


그것은 반복하지 않으면 계산할 수 없다는 좋은 지적입니다. 카운트 기능은 IEnumerable을 구현하는 클래스와 연결되어 있습니다 .... 따라서 IEnumerable이 들어오는 유형을 확인하고 (캐스팅으로 확인) List <> 및 Dictionary <>가 계산하고 사용하는 특정 방법이 있음을 알고 있어야합니다 당신이 유형을 알고 난 후에야. 이 스레드는 개인적으로 매우 유용하다는 것을 알았으므로 Chris와 답장을 보내 주셔서 감사합니다.
PositiveGuy

1
Daniel의 답변 에 따르면 이 대답은 엄격하지 않습니다. 구현은 개체가 "Count"필드가있는 "ICollection"을 구현하는지 확인합니다. 그렇다면 사용합니다. (08 년에 그것이 똑똑한 지 여부는 모르겠습니다.)
ToolmakerSteve


8

아니요, 일반적으로 아닙니다. 열거 형을 사용할 때 한 가지 요점은 열거의 실제 개체 집합을 알 수 없다는 것입니다 (사전 또는 전혀 아님).


중요한 점은 IEnumerable 객체를 가져와도 유형을 파악하기 위해 캐스팅 할 수 있는지 확인해야한다는 것입니다. 그것은 내 코드에서 나처럼 IEnumerable을 더 많이 사용하려는 사람들에게 매우 중요한 포인트입니다.
PositiveGuy

8

System.Linq를 사용할 수 있습니다.

using System;
using System.Collections.Generic;
using System.Linq;

public class Test
{
    private IEnumerable<string> Tables
    {
        get {
             yield return "Foo";
             yield return "Bar";
         }
    }

    static void Main()
    {
        var x = new Test();
        Console.WriteLine(x.Tables.Count());
    }
}

결과는 '2'입니다.


3
이것은 비 제네릭 변형 IEnumerable (유형 지정자 제외)에는 작동하지 않습니다.
Marcel

IEnumerable이있는 경우 .Count의 구현은 모든 항목을 열거합니다. (ICollection과 다릅니다). OP 질문은 분명히 "반복없이"
JDC

5

열거 형을 처리하는 동안 진행 상황을보고하려는 경우 즉각적인 질문 (음수로 철저하게 대답 됨)을 넘어 서면 내 블로그 게시물 인 Linq 쿼리 중 진행 상황을보고 싶을 수 있습니다 .

이를 통해 다음을 수행 할 수 있습니다.

BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += (sender, e) =>
      {
          // pretend we have a collection of 
          // items to process
          var items = 1.To(1000);
          items
              .WithProgressReporting(progress => worker.ReportProgress(progress))
              .ForEach(item => Thread.Sleep(10)); // simulate some real work
      };

4

전달 된 IEnumberable내용 을 확인하기 위해 메소드 내부에서 이러한 방법을 사용했습니다.

if( iEnum.Cast<Object>().Count() > 0) 
{

}

다음과 같은 방법으로 :

GetDataTable(IEnumberable iEnum)
{  
    if (iEnum != null && iEnum.Cast<Object>().Count() > 0) //--- proceed further

}

1
왜 그럴까요? "Count"는 비쌀 수 있으므로 IEnumerable을 사용하면 필요한 모든 출력을 적절한 기본값으로 초기화 한 다음 "iEnum"을 반복하여 시작하는 것이 더 저렴합니다. 루프를 실행하지 않는 빈 "iEnum"이 유효한 결과로 끝나도록 기본값을 선택하십시오. 때때로 이것은 루프가 실행되었는지 알기 위해 부울 플래그를 추가하는 것을 의미합니다. 그것이 서투른 것이지만, "카운트"에 의존하는 것은 현명하지 않은 것 같습니다. 이 플래그가 필요한 경우 코드는 다음과 같습니다 bool hasContents = false; if (iEnum != null) foreach (object ob in iEnum) { hasContents = true; ... your code per ob ... }.
ToolmakerSteve

... 그것은 나 만 반복에, 처음에만 수행해야합니다 특별한 코드를 추가하는 것이 쉽게 다른 처음보다 더를`... {경우 {hasContents = TRUE (hasContents!) .. 일회성 코드 ..; } else {..code for all first first ..} ...} "이 방법은 일회성 코드가 if, 루프 전에, 그러나 비용이" .Count () "가 문제가 될 수 있습니다. 그러면 갈 길입니다.
ToolmakerSteve

3

.Net의 버전과 IEnumerable 객체의 구현에 따라 다릅니다. Microsoft는 IEnumerable.Count 메서드를 수정하여 구현을 확인했으며 ICollection.Count 또는 ICollection <TSource> .Count를 사용합니다. 자세한 내용은 여기를 참조 하십시오 https://connect.microsoft.com/VisualStudio/feedback/details/454130

아래는 System.Linq가있는 Ildasm의 System.Core 용 MSIL입니다.

.method public hidebysig static int32  Count<TSource>(class 

[mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> source) cil managed
{
  .custom instance void System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       85 (0x55)
  .maxstack  2
  .locals init (class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource> V_0,
           class [mscorlib]System.Collections.ICollection V_1,
           int32 V_2,
           class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> V_3)
  IL_0000:  ldarg.0
  IL_0001:  brtrue.s   IL_000e
  IL_0003:  ldstr      "source"
  IL_0008:  call       class [mscorlib]System.Exception System.Linq.Error::ArgumentNull(string)
  IL_000d:  throw
  IL_000e:  ldarg.0
  IL_000f:  isinst     class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>
  IL_0014:  stloc.0
  IL_0015:  ldloc.0
  IL_0016:  brfalse.s  IL_001f
  IL_0018:  ldloc.0
  IL_0019:  callvirt   instance int32 class [mscorlib]System.Collections.Generic.ICollection`1<!!TSource>::get_Count()
  IL_001e:  ret
  IL_001f:  ldarg.0
  IL_0020:  isinst     [mscorlib]System.Collections.ICollection
  IL_0025:  stloc.1
  IL_0026:  ldloc.1
  IL_0027:  brfalse.s  IL_0030
  IL_0029:  ldloc.1
  IL_002a:  callvirt   instance int32 [mscorlib]System.Collections.ICollection::get_Count()
  IL_002f:  ret
  IL_0030:  ldc.i4.0
  IL_0031:  stloc.2
  IL_0032:  ldarg.0
  IL_0033:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::GetEnumerator()
  IL_0038:  stloc.3
  .try
  {
    IL_0039:  br.s       IL_003f
    IL_003b:  ldloc.2
    IL_003c:  ldc.i4.1
    IL_003d:  add.ovf
    IL_003e:  stloc.2
    IL_003f:  ldloc.3
    IL_0040:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    IL_0045:  brtrue.s   IL_003b
    IL_0047:  leave.s    IL_0053
  }  // end .try
  finally
  {
    IL_0049:  ldloc.3
    IL_004a:  brfalse.s  IL_0052
    IL_004c:  ldloc.3
    IL_004d:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0052:  endfinally
  }  // end handler
  IL_0053:  ldloc.2
  IL_0054:  ret
} // end of method Enumerable::Count


2

IEnumerable.Count () 함수의 결과가 잘못되었을 수 있습니다. 다음은 테스트 할 매우 간단한 샘플입니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections;

namespace Test
{
  class Program
  {
    static void Main(string[] args)
    {
      var test = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
      var result = test.Split(7);
      int cnt = 0;

      foreach (IEnumerable<int> chunk in result)
      {
        cnt = chunk.Count();
        Console.WriteLine(cnt);
      }
      cnt = result.Count();
      Console.WriteLine(cnt);
      Console.ReadLine();
    }
  }

  static class LinqExt
  {
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkLength)
    {
      if (chunkLength <= 0)
        throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0");

      IEnumerable<T> result = null;
      using (IEnumerator<T> enumerator = source.GetEnumerator())
      {
        while (enumerator.MoveNext())
        {
          result = GetChunk(enumerator, chunkLength);
          yield return result;
        }
      }
    }

    static IEnumerable<T> GetChunk<T>(IEnumerator<T> source, int chunkLength)
    {
      int x = chunkLength;
      do
        yield return source.Current;
      while (--x > 0 && source.MoveNext());
    }
  }
}

결과는 (7,7,3,3)이어야하지만 실제 결과는 (7,7,3,17)입니다


1

내가 찾은 가장 좋은 방법은 목록으로 변환하여 계산하는 것입니다.

IEnumerable<T> enumList = ReturnFromSomeFunction();

int count = new List<T>(enumList).Count;

-1

ToList에 전화하는 것이 좋습니다. 예, 열거를 일찍하고 있지만 여전히 항목 목록에 액세스 할 수 있습니다.


-1

최상의 성능을 얻을 수는 없지만 LINQ를 사용하여 IEnumerable의 요소를 계산할 수 있습니다.

public int GetEnumerableCount(IEnumerable Enumerable)
{
    return (from object Item in Enumerable
            select Item).Count();
}

결과는 단순히 "`return Enumerable.Count ();"를 수행하는 것과 어떻게 다릅니 까?
ToolmakerSteve

좋은 질문입니까, 아니면 실제로이 Stackoverflow 질문에 대한 답변입니까?
휴고


-3

나는 사용 IEnum<string>.ToArray<string>().Length하고 잘 작동합니다.


이것은 잘 작동합니다. IEnumerator <Object> .ToArray <Object> .Length
din

1
왜 당신이 " " 보다 3 년 일찍 작성된 Daniel의 높은 대답 에서 이미 제공되는 더 간결하고 빠른 해결책이 아닌 왜 이렇게합니까 IEnum<string>.Count();?
ToolmakerSteve

네 말이 맞아 어떻게 든 Daniel의 대답을 간과했습니다. 어쩌면 구현에서 인용했기 때문에 확장 방법을 구현했다고 생각하고 코드가 적은 솔루션을 찾고 있다고 생각했습니다.
Oliver Kötter

내 대답을 가장 잘 처리하는 방법은 무엇입니까? 삭제해야합니까?
Oliver Kötter

-3

문자열 목록이 있으면 그러한 코드를 사용합니다.

((IList<string>)Table).Count
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.