Đếm các mục từ IEn Countable <T> mà không lặp lại?


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

Giả sử tôi muốn lặp lại những thứ đó và viết một cái gì đó như xử lý #n của #m.

Có cách nào để tôi có thể tìm ra giá trị của m mà không cần lặp lại trước khi lặp chính không?

Tôi hy vọng tôi làm cho mình rõ ràng.

Câu trả lời:


338

IEnumerablekhông hỗ trợ này. Đây là do thiết kế. IEnumerablesử dụng đánh giá lười biếng để có được các yếu tố bạn yêu cầu ngay trước khi bạn cần chúng.

Nếu bạn muốn biết số lượng vật phẩm mà không lặp lại chúng, bạn có thể sử dụng ICollection<T>Count.


38
Tôi thích ICollection hơn IList nếu bạn không cần truy cập danh sách theo chỉ mục.
Michael Meadows

3
Tôi thường chỉ lấy Danh sách và IList theo thói quen. Nhưng đặc biệt nếu bạn muốn tự thực hiện chúng, ICollection sẽ dễ dàng hơn và cũng có thuộc tính Count. Cảm ơn!
Mendelt

21
@Shimmy Bạn lặp lại và đếm các yếu tố. Hoặc bạn gọi Count () từ không gian tên Linq thực hiện điều này cho bạn.
Mendelt

1
Chỉ cần thay thế IEnumerable bằng IList là đủ trong trường hợp bình thường?
Teekin

1
@Helgi Vì IEnumerable được đánh giá một cách lười biếng, bạn có thể sử dụng nó cho những thứ mà IList không thể sử dụng. Bạn có thể xây dựng một hàm trả về một IEnumerable liệt kê tất cả các số thập phân của Pi chẳng hạn. Miễn là bạn không bao giờ cố gắng thuyết minh về kết quả hoàn chỉnh, nó sẽ hoạt động. Bạn không thể tạo một IList chứa Pi. Nhưng đó là tất cả học tập khá. Đối với hầu hết các sử dụng bình thường, tôi hoàn toàn đồng ý. Nếu bạn cần Count, bạn cần IList. :-)
Mendelt

215

Các System.Linq.Enumerable.Countphương pháp khuyến nông trên IEnumerable<T>có thực hiện như sau:

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;

Vì vậy, nó cố gắng chuyển sang ICollection<T>, trong đó có một Counttài sản, và sử dụng nó nếu có thể. Nếu không thì nó lặp đi lặp lại.

Vì vậy, cách tốt nhất của bạn là sử dụng Count()phương thức mở rộng trên IEnumerable<T>đối tượng của mình , vì bạn sẽ có được hiệu suất tốt nhất có thể theo cách đó.


13
Rất thú vị điều mà nó cố gắng để ICollection<T>đầu tiên.
Oscar Mederos

1
@OscarMederos Hầu hết các phương thức mở rộng trong Enumerable đều tối ưu hóa cho các chuỗi các loại khác nhau, nơi chúng sẽ sử dụng cách rẻ hơn nếu có thể.
Shibumi

1
Phần mở rộng được đề cập có sẵn kể từ .Net 3.5 và được ghi lại trong MSDN .
Christian

Tôi nghĩ rằng bạn không cần kiểu generic Ttrên IEnumerator<T> enumerator = source.GetEnumerator(), đơn giản bạn có thể làm điều này: IEnumerator enumerator = source.GetEnumerator()và nên làm việc!
Jaider

7
@Jaider - nó hơi phức tạp hơn thế. IEnumerable<T>kế thừa IDisposablecho phép usingcâu lệnh tự động loại bỏ nó. IEnumerablekhông làm. Vì vậy, nếu bạn gọi GetEnumeratormột trong hai cách, bạn nên kết thúc vớivar d = e as IDisposable; if (d != null) d.Dispose();
Daniel Earwicker

87

Chỉ cần thêm một số thông tin:

Phần Count()mở rộng không phải lúc nào cũng lặp đi lặp lại. Hãy xem xét Linq cho Sql, nơi đếm đến cơ sở dữ liệu, nhưng thay vì đưa tất cả các hàng trở lại, nó sẽ đưa ra Count()lệnh Sql và trả về kết quả đó thay vào đó.

Ngoài ra, trình biên dịch (hoặc thời gian chạy) đủ thông minh để nó gọi Count()phương thức đối tượng nếu có. Vì vậy, nó không như những người trả lời khác nói, hoàn toàn không biết gì và luôn lặp đi lặp lại để đếm các yếu tố.

Trong nhiều trường hợp, lập trình viên chỉ kiểm tra if( enumerable.Count != 0 )bằng Any()phương pháp mở rộng, vì if( enumerable.Any() ) hiệu quả hơn với đánh giá lười biếng của linq vì nó có thể bị đoản mạch một khi nó có thể xác định có bất kỳ yếu tố nào. Nó cũng dễ đọc hơn


2
Liên quan đến các bộ sưu tập và mảng. Nếu bạn tình cờ sử dụng một bộ sưu tập thì hãy sử dụng thuộc .Counttính vì nó luôn biết kích thước của nó. Khi truy vấn collection.Countkhông có tính toán bổ sung, nó chỉ đơn giản trả về số lượng đã biết. Tương tự Array.lengthnhư tôi biết. Tuy nhiên, .Any()có được liệt kê của nguồn sử dụng using (IEnumerator<TSource> enumerator = source.GetEnumerator())và trả về true nếu nó có thể làm được enumerator.MoveNext(). Đối với các bộ sưu tập : if(collection.Count > 0), mảng: if(array.length > 0)và trên bảng liệt kê làm if(collection.Any()).
Không

1
Điểm đầu tiên không hoàn toàn đúng ... LINQ to SQL sử dụng phương thức mở rộng này không giống với phương thức này . Nếu bạn sử dụng lần thứ hai, số đếm được thực hiện trong bộ nhớ, không phải là hàm SQL
AlexFoxGill

1
@AlexFoxGill là chính xác. Nếu bạn cast một cách rõ ràng của bạn IQueryably<T>đến một IEnumerable<T>nó sẽ không phát hành một số sql. Khi tôi viết bài này, Linq to Sql là mới; Tôi nghĩ rằng tôi chỉ đang cố gắng sử dụng Any()vì đối với vô số, bộ sưu tập và sql, nó hiệu quả và dễ đọc hơn nhiều (nói chung). Cảm ơn đã cải thiện câu trả lời.
Robert Paulson

12

Một người bạn của tôi có một loạt các bài đăng trên blog cung cấp một minh họa cho lý do tại sao bạn không thể làm điều này. Anh ta tạo ra hàm trả về một số IE trong đó mỗi lần lặp trả về số nguyên tố tiếp theo, tất cả các cách ulong.MaxValuevà mục tiếp theo không được tính cho đến khi bạn yêu cầu. Câu hỏi nhanh, pop: có bao nhiêu mặt hàng được trả lại?

Dưới đây là những bài viết, nhưng chúng khá dài:

  1. Beyond Loops (cung cấp một lớp EnumerableUtility ban đầu được sử dụng trong các bài viết khác)
  2. Các ứng dụng của Iterate (Triển khai ban đầu)
  3. Phương pháp mở rộng điên rồ: ToLazyList (Tối ưu hóa hiệu suất)

2
Tôi thực sự mong muốn MS đã xác định một cách để yêu cầu liệt kê để mô tả những gì họ có thể về bản thân họ (với "không biết gì" là một câu trả lời hợp lệ). Không có vô số nên có bất kỳ khó khăn nào khi trả lời các câu hỏi như "Bạn có biết bản thân mình là hữu hạn", "Bạn có biết bản thân mình là hữu hạn với ít hơn N yếu tố" và "Bạn có biết mình là vô hạn" không, vì bất kỳ điều gì cũng có thể hợp pháp (nếu không có ích) trả lời "không" cho tất cả bọn họ. Nếu có một phương tiện tiêu chuẩn để đặt những câu hỏi như vậy, sẽ an toàn hơn nhiều cho các điều tra viên trả lại chuỗi bất tận ...
supercat

1
... (vì họ có thể nói rằng họ làm như vậy) và đối với mã để giả định rằng các điều tra viên không yêu cầu trả lại chuỗi bất tận có khả năng bị ràng buộc. Lưu ý rằng bao gồm một phương tiện để hỏi những câu hỏi như vậy (để giảm thiểu nồi hơi, có thể có một tài sản trả lại một EnumerableFeaturesđối tượng) sẽ không yêu cầu điều tra viên làm bất cứ điều gì khó khăn, nhưng có thể hỏi những câu hỏi như vậy (cùng với một số người khác như "Bạn có thể hứa không luôn trả về cùng một chuỗi các mục "," Bạn có thể tiếp xúc với mã một cách an toàn không nên thay đổi bộ sưu tập cơ bản của bạn không ", v.v.) sẽ rất hữu ích.
supercat

Điều đó sẽ khá tuyệt, nhưng bây giờ tôi chắc chắn rằng nó sẽ kết hợp tốt như thế nào với các khối lặp. Bạn sẽ cần một số loại "tùy chọn năng suất" đặc biệt hoặc một cái gì đó. Hoặc có thể sử dụng các thuộc tính để trang trí phương thức lặp.
Joel Coehoorn

1
Các khối lặp có thể, trong trường hợp không có bất kỳ khai báo đặc biệt nào khác, chỉ cần báo cáo rằng chúng không biết gì về chuỗi được trả về, mặc dù nếu IEnumerator hoặc một người kế thừa được chứng thực bởi MS (có thể được trả về bởi các triển khai GetEnumerator biết về sự tồn tại của nó) là để hỗ trợ thêm thông tin, C # có thể sẽ nhận được một set yield optionstuyên bố hoặc một cái gì đó tương tự để hỗ trợ nó. Nếu được thiết kế hợp lý, một thứ IEnhancedEnumeratorcó thể khiến những thứ như LINQ trở nên hữu dụng hơn rất nhiều bằng cách loại bỏ rất nhiều "cuộc phòng thủ" ToArrayhoặc ToListcác cuộc gọi, đặc biệt là ...
supercat

... trong trường hợp những thứ như Enumerable.Concatđược sử dụng để kết hợp một bộ sưu tập lớn mà biết rất nhiều về chính nó với một bộ sưu tập nhỏ thì không.
19/2/2015

10

Vô số không thể đếm mà không lặp.

Trong các trường hợp "bình thường", các lớp triển khai IEnumerable hoặc IEnumerable <T>, chẳng hạn như List <T>, có thể thực hiện phương thức Count bằng cách trả về thuộc tính List <T> .Count. Tuy nhiên, phương thức Count không thực sự là một phương thức được xác định trên giao diện IEnumerable <T> hoặc IEnumerable. (Trên thực tế, thứ duy nhất là GetEnumerator.) Và điều này có nghĩa là việc triển khai cụ thể theo lớp không thể được cung cấp cho nó.

Thay vào đó, Count nó là một phương thức mở rộng, được định nghĩa trên lớp tĩnh Enumerable. Điều này có nghĩa là nó có thể được gọi trong bất kỳ trường hợp nào của lớp dẫn xuất <T> IEnumerable, bất kể việc triển khai của lớp đó là gì. Nhưng nó cũng có nghĩa là nó được thực hiện ở một nơi duy nhất, bên ngoài bất kỳ lớp nào trong số đó. Tất nhiên điều đó có nghĩa là nó phải được thực hiện theo cách hoàn toàn độc lập với nội bộ của các lớp này. Cách duy nhất để làm việc đếm là thông qua lặp.


đó là một điểm tốt về việc không thể đếm trừ khi bạn lặp đi lặp lại. Chức năng đếm được gắn với các lớp triển khai IEnumerable .... do đó bạn phải kiểm tra loại IEnumerable nào đang đến (kiểm tra bằng cách truyền) và sau đó bạn biết rằng Danh sách <> và Từ điển <> có một số cách nhất định để đếm và sau đó sử dụng những người chỉ sau khi bạn biết loại. Tôi thấy chủ đề này rất hữu ích cá nhân vì vậy cảm ơn bạn đã trả lời Chris.
positiveGuy

1
Theo câu trả lời của Daniel, câu trả lời này không hoàn toàn đúng: việc triển khai DOES kiểm tra xem đối tượng có thực hiện "ICollection" hay không, có trường "Đếm". Nếu vậy, nó sử dụng nó. (Cho dù đó là thông minh trong '08, tôi không biết.)
ToolmakerSteve


8

Không, không nói chung. Một điểm trong việc sử dụng liệt kê là tập hợp các đối tượng thực tế trong bảng liệt kê không được biết (trước, hoặc thậm chí là tất cả).


Điểm quan trọng bạn đưa ra là ngay cả khi bạn có được đối tượng IEnumerable đó, bạn phải xem liệu bạn có thể sử dụng nó để tìm ra loại đó không. Đó là một điểm rất quan trọng đối với những người đang cố gắng sử dụng nhiều IE hơn như tôi trong mã của tôi.
positiveGuy

8

Bạn có thể sử dụng 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());
    }
}

Bạn sẽ nhận được kết quả '2'.


3
Điều này không hoạt động đối với biến thể không chung chung IEnumerable (không có trình xác định kiểu)
Marcel

Việc triển khai .Count là liệt kê tất cả các mục nếu bạn có IEnumerable. (khác nhau cho ICollection). Câu hỏi OP rõ ràng là "không lặp lại"
JDC

5

Vượt ra ngoài câu hỏi ngay lập tức của bạn (đã được trả lời kỹ lưỡng trong phần phủ định), nếu bạn đang muốn báo cáo tiến trình trong khi xử lý vô số, bạn có thể muốn xem bài đăng trên blog của tôi Báo cáo tiến trình trong các câu hỏi Linq .

Nó cho phép bạn làm điều này:

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

Tôi đã sử dụng cách đó trong một phương thức để kiểm tra IEnumberablenội dung được truyền

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

}

Bên trong một phương thức như thế này:

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

}

1
Tại sao làm điều đó? "Đếm" có thể tốn kém, do đó, với một IEnumerable, sẽ rẻ hơn khi khởi tạo bất kỳ đầu ra nào bạn cần để mặc định phù hợp, sau đó bắt đầu lặp lại qua "iEnum". Chọn mặc định sao cho "iEnum" trống, không bao giờ thực hiện vòng lặp, kết thúc với kết quả hợp lệ. Đôi khi, điều này có nghĩa là thêm một cờ boolean để biết liệu vòng lặp đã được thực thi hay chưa. Cho rằng đó là vụng về, nhưng dựa vào "Count" có vẻ không khôn ngoan. Nếu cần cờ này, mã trông như : bool hasContents = false; if (iEnum != null) foreach (object ob in iEnum) { hasContents = true; ... your code per ob ... }.
ToolmakerSteve

... Thật dễ dàng để thêm mã đặc biệt chỉ nên được thực hiện lần đầu tiên hoặc chỉ trên các lần lặp khác với lần đầu tiên: `... {if (! hasContents) {hasContents = true; .. một mã thời gian ..; } other {..code cho tất cả trừ lần đầu tiên ..} ...} "Phải thừa nhận rằng điều này vụng về hơn cách tiếp cận đơn giản của bạn, trong đó mã một lần sẽ ở bên trong if, trước vòng lặp, nhưng nếu chi phí là" .Count () "có thể là một mối quan tâm, sau đó đây là cách để đi.
ToolmakerSteve

3

Nó phụ thuộc vào phiên bản nào của .Net và việc triển khai đối tượng IEnumerable của bạn. Microsoft đã sửa phương thức IEnumerable.Count để kiểm tra việc triển khai và sử dụng ICollection.Count hoặc ICollection <TSource> .Count, xem chi tiết tại đây https://connect.microsoft.com/VisualStudio/feedback/details/454130

Và bên dưới là MSIL từ Ildasm cho System.Core, trong đó System.Linq cư trú.

.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

Kết quả của hàm IEnumerable.Count () có thể sai. Đây là một mẫu rất đơn giản để kiểm tra:

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

Kết quả phải là (7,7,3,3) nhưng kết quả thực tế là (7,7,3,17)


1

Cách tốt nhất tôi tìm thấy là tính bằng cách chuyển đổi nó thành một danh sách.

IEnumerable<T> enumList = ReturnFromSomeFunction();

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

-1

Tôi sẽ đề nghị gọi ToList. Có bạn đang thực hiện liệt kê sớm, nhưng bạn vẫn có quyền truy cập vào danh sách các mặt hàng của bạn.


-1

Nó có thể không mang lại hiệu suất tốt nhất, nhưng bạn có thể sử dụng LINQ để đếm các yếu tố trong IEnumerable:

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

Làm thế nào để kết quả khác với chỉ đơn giản là thực hiện "` return Enumerable.Count (); "?
ToolmakerSteve

Câu hỏi hay, hoặc nó thực sự là một câu trả lời cho câu hỏi Stackoverflow này.
Hugo

-1

Tôi nghĩ rằng cách dễ nhất để làm điều này

Enumerable.Count<TSource>(IEnumerable<TSource> source)

Tham khảo: system.linq.enumerable


Câu hỏi là "Đếm các mục từ <T> IEn mà không lặp lại?" Làm thế nào để trả lời này?
Enigmativity

-3

Tôi sử dụng IEnum<string>.ToArray<string>().Lengthvà nó hoạt động tốt.


Điều này sẽ làm việc tốt. IEnumerator <Object> .ToArray <Object> .Ldrops
din

1
Tại sao bạn làm điều này, thay vì giải pháp ngắn gọn và hiệu quả nhanh hơn đã được đưa ra trong câu trả lời được đánh giá cao của Daniel được viết sớm hơn ba năm so với của bạn , " IEnum<string>.Count();"?
ToolmakerSteve

Bạn đúng rồi. Bằng cách nào đó tôi đã bỏ qua câu trả lời của Daniel, có thể vì anh ta trích dẫn từ việc triển khai và tôi nghĩ rằng anh ta đã thực hiện một phương pháp mở rộng và tôi đang tìm kiếm một giải pháp với ít mã hơn.
Oliver Kötter ngày

Cách xử lý được chấp nhận nhất của tôi là gì? Tôi có nên xóa nó?
Oliver Kötter

-3

Tôi sử dụng mã như vậy, nếu tôi có danh sách các chuỗi:

((IList<string>)Table).Count
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.