IEnumerable <T> öğelerini yinelemeden saymak ister misiniz?


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

Diyelim ki bunlar üzerinde yineleme istiyorum ve #n # # işleme gibi bir şey yazıyorum.

Ana yinelememden önce yinelemeden m'nin değerini öğrenmenin bir yolu var mı?

Umarım kendimi açıklığa kavuşturmuşumdur.

Yanıtlar:


338

IEnumerablebunu desteklemiyor. Bu tasarım gereğidir. IEnumerableİstediğiniz öğeleri ihtiyaç duymadan hemen önce almak için tembel değerlendirme kullanır.

Kullanabileceğiniz öğelerin sayısını yinelemeden bilmek istiyorsanız ICollection<T>, bir Countözelliği vardır.


38
Listeye indexer tarafından erişmeniz gerekmiyorsa IList üzerinden ICollection'ı tercih ederim.
Michael Meadows

3
List ve IListleri alışkanlıktan alıyorum. Ancak özellikle bunları kendiniz uygulamak istiyorsanız ICollection daha kolaydır ve ayrıca Count özelliğine sahiptir. Teşekkürler!
Mendelt

21
@Shimmy Elemanları yineleyip sayıyorsunuz. Ya da sizin için bunu yapan Linq ad alanından Count () öğesini çağırırsınız.
Mendelt

1
Normal koşullarda IEnumerable yerine IList koymak yeterli midir?
Teekin

1
@Helgi IEnumerable tembel olarak değerlendirildiğinden, IList'in kullanılamayacağı şeyler için kullanabilirsiniz. Örneğin, Pi'nin tüm ondalık sayılarını numaralandıran bir IEnumerable döndüren bir işlev oluşturabilirsiniz. Tam sonuç üzerinde asla foreach yapmaya çalışmadığınız sürece, sadece çalışmalıdır. Pi içeren bir IList yapamazsınız. Ama hepsi oldukça akademik. Çoğu normal kullanım için tamamen katılıyorum. Count'a ihtiyacınız varsa IList'e ihtiyacınız var. :-)
Mendelt

215

Üzerindeki System.Linq.Enumerable.Countuzantı yöntemi IEnumerable<T>aşağıdaki uygulamaya sahiptir:

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;

Bu nedenle ICollection<T>, bir Countözelliğe sahip olan yayın yapmaya çalışır ve mümkünse bunu kullanır. Aksi takdirde yinelenir.

Bu yüzden en iyi seçeneğiniz, nesnenizdeki Count()uzantı yöntemini kullanmaktır IEnumerable<T>, çünkü bu şekilde mümkün olan en iyi performansı elde edersiniz.


13
ICollection<T>İlk önce atmaya çalıştığı şey çok ilginç .
Oscar Mederos

1
@OscarMederos Enumerable'daki genişletme yöntemlerinin çoğunda, mümkün olduğunca daha ucuz yolu kullanacakları çeşitli türdeki diziler için optimizasyonlar bulunur.
Shibumi

1
Bahsedilen uzantı .Net 3.5'ten beri mevcuttur ve MSDN'de belgelenmiştir .
Christian

Sana genel tür gerekmez düşünüyorum Tüzerine IEnumerator<T> enumerator = source.GetEnumerator(): sadece bunu yapabilirsiniz IEnumerator enumerator = source.GetEnumerator()ve çalışmalı!
Jaider

7
@Jaider - bundan biraz daha karmaşık. ifadenin otomatik olarak atılmasına izin veren IEnumerable<T>miras alır . değil. Yani her iki şekilde de ararsanız , bitirmelisinizIDisposableusingIEnumerableGetEnumeratorvar d = e as IDisposable; if (d != null) d.Dispose();
Daniel Earwicker 7:15

87

Ekstra bilgi eklemek yeterlidir:

Count()Uzatma hep yineleme etmez. Sayının veritabanına gittiği Linq to Sql'yi düşünün, ancak tüm satırları geri getirmek yerine Sql Count()komutunu verir ve bunun yerine bu sonucu döndürür.

Ayrıca, derleyici (veya çalışma zamanı), Count()eğer varsa , nesne yöntemini çağıracak kadar akıllıdır . Yani bu kadar değil diğer müdahale tamamen cahil olmak ve her zaman elemanlarını saymak amacıyla yineleme, dedikleri gibi.

Programlayıcının sadece uzatma yöntemini if( enumerable.Count != 0 )kullanarak kontrol ettiği birçok durumda Any(), çünkü if( enumerable.Any() ) linq'in tembel değerlendirmesinde çok daha etkilidir, çünkü herhangi bir eleman olduğunu belirleyebildiğinden kısa devre yapabilir. Ayrıca daha okunabilir


2
Koleksiyonlar ve diziler konusunda. Bir koleksiyon kullanırsanız, .Countözelliği her zaman boyutunu bildiği gibi kullanın . Başka bir collection.Counthesaplama olmadığında sorgulanırken , zaten bilinen sayıyı döndürür. Array.lengthBildiğim kadarıyla aynı . Bununla birlikte, .Any()kaynağın numaralandırıcısını kullanarak alır using (IEnumerator<TSource> enumerator = source.GetEnumerator())ve eğer yapabilirse true değerini döndürür enumerator.MoveNext(). Koleksiyonlar için:, if(collection.Count > 0)diziler: if(array.length > 0)ve numaralandırılabilirler üzerinde yapın if(collection.Any()).
Hayır

1
Birinci nokta kesinlikle doğru değildir ... LINQ to SQL kullanır bu uzantı yöntemi ile aynı değildir bu bir . İkincisini kullanırsanız, sayı bir SQL işlevi olarak değil bellekte gerçekleştirilir
AlexFoxGill 9

1
@AlexFoxGill doğru. Açıkça Kadronuzun Eğer IQueryably<T>bir karşı IEnumerable<T>bir sql sayımını düzenlemez. Bunu yazdığımda Linq to Sql yeniydi; Ben sadece kullanmak Any()için itmek düşünüyorum çünkü numaralandırılabilir, koleksiyon ve sql için çok daha verimli ve okunabilir (genel olarak). Cevabı geliştirdiğiniz için teşekkürler.
Robert Paulson

12

Bir arkadaşımın bunu neden yapamayacağınıza dair bir örnek sağlayan bir dizi blog yazısı var. Her yinelemenin bir sonraki asal sayıyı sonuna kadar döndürdüğü ulong.MaxValueve bir sonraki öğe siz sorana kadar hesaplanmadığı bir IEnumerable döndüren işlev oluşturur . Hızlı, pop soru: kaç öğe iade edildi?

İşte gönderiler, ama biraz uzun:

  1. Beyond Loops (diğer yazılarda kullanılan bir ilk EnumerableUtility sınıfı sağlar)
  2. İterat Uygulamaları (İlk uygulama)
  3. Çılgın Uzantı Yöntemleri: ToLazyList (Performans optimizasyonları)

2
Gerçekten MS üye numaralarından kendileri hakkında neler yapabileceğini anlatmak için bir yol tanımlamış olmasını dilerdim ("hiçbir şey bilmeden" geçerli bir cevap). Hiçbir numaralandırıcının "Sonlu olduğunuzu biliyor musunuz", "N öğesinden daha azıyla kendiniz sınırlı olduğunuzu biliyor musunuz?" Ve "Sonsuz olduğunuzu biliyor musunuz?" Gibi soruları yanıtlamakta zorlanmamalıdır. (yararsızsa) hepsine "hayır" cevabı verin. Bu tür sorular sormanın standart bir yolu olsaydı, numaralandırıcılar için sonsuz dizileri geri döndürmek çok daha güvenli olurdu ...
supercat

1
... (bunu yaptıklarını söyleyebildikleri için) ve kodun, sonsuz dizileri döndürdüğünü iddia etmeyen numaralandırıcıların muhtemelen sınırlandırılacağını varsaymaları için. Bu tür soruları sormanın bir yolunu dahil etmenin (kazan plakasını en aza indirmek, muhtemelen bir EnumerableFeaturesnesnenin geri dönüşünü sağlamak için ) sayım görevlilerinin zor bir şey yapmasını gerektirmeyeceğini, ancak bu tür soruları ("Söz verebilir misiniz?" her zaman aynı öğe sırasını döndürün "," Güvenli bir şekilde temel koleksiyonunuzu değiştirmemesi gereken koda maruz kalabilir misiniz "vb.) çok yararlı olacaktır.
supercat

Bu çok güzel olurdu, ama şimdi bunun yineleyici bloklarla ne kadar iyi bir şekilde bağlanacağından eminim. Bir çeşit özel "verim seçeneklerine" falan ihtiyacınız var. Veya yineleyici yöntemini süslemek için öznitelikleri kullanın.
Joel Coehoorn

1
Iterator blokları, başka herhangi bir özel bildirimin yokluğunda, döndürülen dizi hakkında hiçbir şey bilmediklerini bildirebilir, ancak IEnumerator veya MS onaylı bir halef (varlığını bilen GetEnumerator uygulamaları tarafından döndürülebilir) ek bilgileri destekleyecek olsaydı, C # muhtemelen set yield optionsbunu destekleyecek bir deyim ya da buna benzer bir şey alırdı . Düzgün tasarlanmışsa, bir IEnhancedEnumerator"savunma" ToArrayya da ToListçağrıları, özellikle de ortadan kaldırarak LINQ gibi şeyler çok daha kullanışlı hale getirebilir ...
supercat

... Enumerable.Concatkendisi hakkında çok şey bilen büyük bir koleksiyonu küçük bir koleksiyonla birleştirmek gibi şeylerin kullanıldığı durumlarda .
supercat

10

IEnumerable yinelemeden sayamaz.

"Normal" koşullar altında, Liste <T> gibi IEnumerable veya IEnumerable <T> uygulayan sınıfların List <T> .Count özelliğini döndürerek Count yöntemini uygulaması mümkün olabilir. Ancak, Count yöntemi aslında IEnumerable <T> veya IEnumerable arabiriminde tanımlanan bir yöntem değildir. (Aslında tek olan GetEnumerator'tur.) Ve bu, sınıfa özgü bir uygulamanın sağlanamayacağı anlamına gelir.

Aksine, Say say statik sınıfında tanımlanan bir uzantı yöntemidir. Bu, söz konusu sınıfın uygulamasından bağımsız olarak IEnumerable <T> türetilmiş sınıfın herhangi bir örneğinde çağrılabileceği anlamına gelir. Ancak aynı zamanda, bu sınıfların herhangi birinin dışında tek bir yerde uygulandığı anlamına gelir. Tabii ki bu, bu sınıfın içlerinden tamamen bağımsız bir şekilde uygulanması gerektiği anlamına gelir. Saymayı yapmanın tek yolu yinelemedir.


bu, tekrar etmedikçe sayamamanın iyi bir noktası. Sayma işlevselliği IEnumerable uygulayan sınıflara bağlıdır .... böylece hangi tür IEnumerable gelen (döküm tarafından kontrol) kontrol edin ve sonra Liste <> ve Sözlük <> saymak ve sonra kullanmak için belirli yolları olduğunu bilmek zorunda sadece tipini öğrendikten sonra. Bu konuyu kişisel olarak çok faydalı buldum, bu yüzden cevabınız için teşekkürler Chris.
PositiveGuy

1
Göre Daniel'in cevabı bu yanıtın kesinlikle doğru değildir: has nesne uygular "ıcollection" Bir alanı "Sayı" eğer uygulama kontrolünü YAPAR. Eğer öyleyse kullanır. ('08'de o kadar akıllı olsaydı, bilmiyorum.)
ToolmakerSteve


8

Hayır, genel olarak değil. Numaralandırılabilirleri kullanmanın bir noktası, numaralandırmadaki gerçek nesne kümesinin bilinmemesidir (önceden veya hatta hiç).


Getirdiğiniz önemli nokta, bu IEnumerable nesnesini elde ettiğinizde bile, bunun ne tür olduğunu bulmak için onu döküp dökemeyeceğinizi görmeniz gerektiğidir. Kodumda kendim gibi daha fazla IEnumerable kullanmaya çalışanlar için bu çok önemli bir nokta.
PositiveGuy

8

System.Linq kullanabilirsiniz.

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' sonucunu alırsınız.


3
Bu, jenerik olmayan IEnumerable varyantı için çalışmaz (tip belirteci olmadan)
Marcel

Bir IEnumerable varsa .Count uygulaması tüm öğeleri numaralandırıyor. (ICollection için farklı). OP sorusu açıkça "tekrarlamadan"
JDC

5

Anlık sorunuzun ötesine geçerek (negatif olarak iyice cevaplanmıştır), bir numaralandırılabilir işlemi gerçekleştirirken ilerlemeyi rapor etmek istiyorsanız, blog yayınım Linq Sorguları Sırasında Raporlama İlerlemesi'ne bakmak isteyebilirsiniz .

Bunu yapmanıza izin verir:

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İçeriğe aktarılan içeriği kontrol etmek için bir yöntem içinde böyle kullandım

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

}

Böyle bir yöntem içinde:

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

}

1
Neden bunu yapıyorsun? "Sayım" pahalı olabilir, bu nedenle bir IEnumerable göz önüne alındığında, uygun varsayılanlara ihtiyacınız olan çıktıları başlatmak, sonra "iEnum" üzerinden yinelemeyi başlatmak daha ucuzdur. Döngüyü asla yürütmeyen boş bir "iEnum" geçerli bir sonuçla sonuçlanacak şekilde varsayılanları seçin. Bazen bu, döngünün yürütülüp yürütülmediğini bilmek için bir boole bayrağı eklemek anlamına gelir. Bu beceriksiz ama "Kont" a güvenmek mantıksız görünüyor. Gibi bu bayrağı, kod görünüyor gerekirse: bool hasContents = false; if (iEnum != null) foreach (object ob in iEnum) { hasContents = true; ... your code per ob ... }.
ToolmakerSteve

... o yoksa yalnızca yineleme, sadece ilk defa yapılmalıdır özel bir kod eklemek de kolaydır diğer ilk kez daha: `... {if {hasContents = true; (hasContents!) ..bir zaman kodu ..; } else {..code, ilk kez hariç herkes için ..} ...} "Kuşkusuz, bu basit yaklaşımınızdan daha hantaldır, burada bir kerelik kod if'nizin içinde, döngüden önce, ancak maliyeti ise" .Count () "bir sorun olabilir, o zaman bu gitmek için bir yoldur.
ToolmakerSteve

3

.Net'in hangi sürümüne ve IEnumerable nesnenizin uygulanmasına bağlıdır. Microsoft, uygulamayı denetlemek için IEnumerable.Count yöntemini düzeltti ve ICollection.Count veya ICollection <TSource> .Count kullanıyor, ayrıntılara buradan bakın https://connect.microsoft.com/VisualStudio/feedback/details/454130

Ve System.Linq'in bulunduğu Ildasm for System.Core'dan MSIL aşağıdadır.

.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 () işlevinin sonucu yanlış olabilir. Bu test etmek için çok basit bir örnektir:

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

Sonuç (7,7,3,3) olmalı ancak gerçek sonuç (7,7,3,17)


1

Bulduğum en iyi yol, listeye dönüştürerek saymak.

IEnumerable<T> enumList = ReturnFromSomeFunction();

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

-1

ToList'i aramanızı öneririm. Evet, numaralandırmayı erkenden yapıyorsunuz, ancak yine de öğe listenize erişebiliyorsunuz.


-1

En iyi performansı vermeyebilir, ancak bir IEnumerable'daki öğeleri saymak için LINQ kullanabilirsiniz:

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

Sonuç, yalnızca "Enumerable.Count (); return" yapmasından nasıl farklıdır?
ToolmakerSteve

Güzel bir soru, ya da aslında bu Stackoverflow sorusuna bir cevap mı.
Hugo


-3

Ben kullanıyorum IEnum<string>.ToArray<string>().Lengthve iyi çalışıyor.


Bu iyi olmalı. IEnumerator <Object> .ToArray <Object>. Uzunluk
din

1
Neden çok daha Özlü daha şimdiden verilen çözüm daha hızlı performans gösteren, bunu yapıyorsunuz seninkinden daha üç yıl önce yazılmış yüksek upvoted cevabı Daniel , " IEnum<string>.Count();"?
ToolmakerSteve

Haklısın. Bir şekilde Daniel'in cevabını göz ardı ettim, belki de uygulamadan alıntı yaptığı için ve bir uzatma yöntemi uyguladığını düşündüm ve daha az kodlu bir çözüm arıyordum.
Oliver Kötter

Kötü cevabımı ele almanın en kabul edilen yolu nedir? Silmeli miyim?
Oliver Kötter

-3

Dizeleri listesi varsa, böyle bir kod kullanın:

((IList<string>)Table).Count
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.