2009-10-28 14 views
5

Tôi đã suy nghĩ về phương pháp IEnumerator.Reset(). Tôi đọc trong tài liệu MSDN rằng nó chỉ có cho COM interop. Là một lập trình viên C++, nó trông giống như một IEnumerator hỗ trợ Reset là những gì tôi gọi là forward iterator, trong khi IEnumerator không hỗ trợ Reset thực sự là một input iterator.Liệu C# có được hưởng lợi từ sự phân biệt giữa các loại điều tra viên, như trình lặp C++ không?

Vì vậy, một phần của câu hỏi của tôi là, sự hiểu biết này có chính xác không?

Phần thứ hai của câu hỏi của tôi là, nó sẽ có lợi ích nào trong C# nếu có sự phân biệt giữa các trình vòng lặp đầu vào và vòng lặp chuyển tiếp (hoặc "điều tra viên" nếu bạn thích)? Nó sẽ không giúp loại bỏ một số nhầm lẫn giữa các lập trình viên, giống như một trong số này được tìm thấy trong số SO question about cloning iterators này?

CHỈNH SỬA: Làm rõ trên các trình vòng lặp chuyển tiếp và đầu vào. Trình lặp đầu vào chỉ đảm bảo rằng bạn có thể liệt kê các thành viên của một bộ sưu tập (hoặc từ một hàm máy phát hoặc một luồng đầu vào) chỉ một lần. Đây chính là cách IEnumerator hoạt động trong C#. Cho dù bạn có thể liệt kê lần thứ hai hay không, được xác định là có hay không Reset được hỗ trợ. Trình vòng lặp chuyển tiếp, không có hạn chế này. Bạn có thể liệt kê các thành viên thường xuyên như bạn muốn.

Một số lập trình viên C# không hiểu tại sao một số IEnumerator không thể được sử dụng một cách đáng tin cậy trong thuật toán đa. Hãy xem xét các trường hợp sau đây:

void PrintContents(IEnumerator<int> xs) 
{ 
    while (iter.MoveNext()) 
    Console.WriteLine(iter.Current); 
    iter.Reset(); 
    while (iter.MoveNext()) 
    Console.WriteLine(iter.Current); 
} 

Nếu chúng ta gọi là PrintContents trong bối cảnh này, không có vấn đề:

List<int> ys = new List<int>() { 1, 2, 3 } 
PrintContents(ys.GetEnumerator()); 

Tuy nhiên nhìn vào những điều sau đây:

IEnumerable<int> GenerateInts() { 
    System.Random rnd = new System.Random(); 
    for (int i=0; i < 10; ++i) 
    yield return Rnd.Next(); 
} 

PrintContents(GenerateInts()); 

Nếu IEnumerator hỗ trợ Reset, trong các từ khác hỗ trợ các thuật toán đa truyền, sau đó mỗi lần bạn lặp lại bộ sưu tập thì nó sẽ khác nhau. Điều này sẽ là không mong muốn, bởi vì nó sẽ là hành vi đáng ngạc nhiên. Ví dụ này hơi giả, nhưng nó xảy ra trong thế giới thực (ví dụ: đọc từ luồng tệp).

+1

Tôi nghĩ bạn có nghĩa là 'IEnumerator.Reset', không phải là 'IEnumerable .Reset', phải không? –

+0

Vâng, cảm ơn bạn! Xin lỗi vì điều đó. – cdiggins

+0

Câu hỏi thú vị. Nhưng có lẽ bạn nên giải thích C++ - nói một chút, vì rất nhiều lập trình viên C# sẽ thấy điều này. Nó có thể không được rõ ràng những gì một iterator đầu vào và chuyển tiếp về phía trước là, chính xác (đặc biệt, khả năng đơn/multipass của họ, đó là những gì thực sự có liên quan đến câu hỏi này) – jalf

Trả lời

2

Câu hỏi thú vị. Việc tôi nhận là tất nhiên C# sẽ hưởng lợi. Tuy nhiên, nó sẽ không dễ dàng để thêm vào.

Sự khác biệt tồn tại trong C++ vì hệ thống kiểu linh hoạt hơn nhiều. Trong C#, bạn không có một cách chung chung mạnh mẽ để sao chép các đối tượng, mà là cần thiết để đại diện cho các vòng lặp chuyển tiếp (để hỗ trợ lặp đi lặp lại nhiều lần). Và tất nhiên, để điều này thực sự hữu ích, bạn cũng cần hỗ trợ các trình vòng lặp/điều tra truy cập hai chiều và ngẫu nhiên. Và để có được tất cả các hoạt động trơn tru, bạn thực sự cần một số hình thức gõ vịt, giống như các mẫu C++ có.

Cuối cùng, phạm vi của hai khái niệm là khác nhau.

Trong C++, trình lặp được cho là đại diện cho mọi thứ bạn cần biết về một loạt các giá trị. Với một cặp lặp, tôi không cần cần vùng chứa ban đầu. Tôi có thể sắp xếp, tôi có thể tìm kiếm, tôi có thể thao tác và sao chép các phần tử nhiều như tôi muốn. Thùng chứa ban đầu nằm ngoài ảnh.

Trong C#, điều tra viên không có ý định làm nhiều như vậy. Cuối cùng, chúng chỉ được thiết kế để cho phép bạn chạy qua chuỗi theo cách tuyến tính.

Đối với Reset(), nó được chấp nhận rộng rãi là một sai lầm khi thêm nó ngay từ đầu. Nếu nó đã làm việc, và được thực hiện một cách chính xác, thì có, bạn có thể nói điều tra viên của bạn tương tự như các trình vòng lặp chuyển tiếp, nhưng nói chung, tốt nhất là bỏ qua nó như là một sai lầm. Và sau đó tất cả các điều tra viên chỉ tương tự như các vòng lặp đầu vào.

Thật không may.

+0

Sau một tách cà phê, tôi không thể thấy tại sao nhân bản là cần thiết cho một iterator đa pass (bỏ qua các yêu cầu đầy đủ của một iterator chuyển tiếp) cho bây giờ. – cdiggins

+0

Làm cách nào khác bạn có thể thực hiện nhiều lần chuyển tiếp trên một trình lặp chuyển tiếp? Nó không phải là hai chiều, vì vậy bạn không thể quay lại nơi bạn đang ở, và lặp lại lần nữa. Bạn phải tạo một bản sao của trình lặp, vì vậy bạn có hai trình vòng lặp trỏ đến cùng một vị trí trong chuỗi, và sau đó chúng có thể được tăng lên riêng lẻ. – jalf

+0

Nếu bạn đang nghĩ rằng phương pháp 'Reset()' có thể thay thế nhân bản, có, loại. Ngoại trừ thiết lập lại chỉ mang lại cho bạn trở lại một điểm cố định trong chuỗi (đầu). Nhưng một trình lặp chuyển tiếp có thể thực hiện nhiều lần truyền trên một tập hợp con của dữ liệu (chỉ có ba phần tử cuối cùng). Đặt lại không thực sự giúp ích ở đó. (hoặc ít nhất, nó sẽ được ridiculously chậm nếu bạn đã phải làm một thiết lập lại đầy đủ, và đi qua toàn bộ trình tự chỉ để quay trở lại nơi bạn muốn thực hiện nhiều vượt qua traversal) – jalf

3

Reset là một sai lầm lớn. Tôi gọi shenanigans trên Reset. Theo tôi, cách chính xác để phản ánh sự khác biệt mà bạn đang thực hiện giữa "chuyển tiếp tiến" và "bộ lặp đầu vào" trong hệ thống kiểu .NET là sự phân biệt giữa IEnumerable<T>IEnumerator<T>.

Xem thêm this answer, nơi Eric Lippert của Microsoft (trong một capactiy không chính thức, không nghi ngờ gì nữa, quan điểm của tôi chỉ là anh ta là người có nhiều thông tin hơn tôi phải đưa ra tuyên bố rằng đây là lỗi thiết kế). bình luận. Đồng thời xem thêm his awesome blog.

+1

1 cho các liên kết. Tuy nhiên, tôi không đồng ý với sự tương tự của bạn. IEnumerable có nhiều phần tử hậu cần hơn đối với một thùng chứa C++ http://www.sgi.com/tech/stl/Container.html – cdiggins

-1

Tôi không nghĩ vậy. Tôi sẽ gọi IEnumerable một trình chuyển tiếp chuyển tiếp và một trình lặp đầu vào. Nó không cho phép bạn quay trở lại, hoặc sửa đổi bộ sưu tập cơ bản. Với việc bổ sung từ khóa foreach, các trình vòng lặp gần như là một phần lớn thời gian.

Ý kiến: Sự khác biệt giữa lặp đầu vào (nhận được mỗi một) vs lặp đầu ra (làm điều gì đó cho mỗi một) là quá tầm thường để biện minh cho một bổ sung cho khung.Ngoài ra, để thực hiện một trình lặp đầu ra, bạn sẽ cần phải chuyển giao một đại biểu cho trình vòng lặp. Trình lặp đầu vào dường như tự nhiên hơn đối với các lập trình viên C#.

Còn có IList<T> nếu người lập trình muốn truy cập ngẫu nhiên.

+0

Trong C++ một trình chuyển tiếp tiến trình hỗ trợ các thuật toán đa truyền, đây không phải là điều bạn có thể tin cậy với IEnumerable/IEnumerator. Ví dụ: nếu IEnumerable được tạo từ một hàm lợi nhuận. – cdiggins

+0

Tôi không hiểu tại sao IEnumerable không hỗ trợ các thuật toán đa truyền. Trình vòng lặp tùy chỉnh (sử dụng trả về lợi nhuận) tạo ra một thể hiện của IEnumerator. Bạn có thể có nhiều đối tượng IEnumerator lặp qua cùng một bộ sưu tập cùng một lúc. –

+2

Nhưng chỉ cho một IEnumerator, bạn không thể làm nhiều hơn một lần vượt qua bộ sưu tập. Bạn cần phải có quyền truy cập vào bộ sưu tập cơ bản để thực hiện lặp lại nhiều lần, đó là một chút thiếu sót. Trong C#, một số thuật toán khá cơ bản (bất cứ điều gì đòi hỏi nhiều hơn một pass) phải phá vỡ trừu tượng và yêu cầu truy cập vào container bên dưới) – jalf

1

Đến từ quan điểm C#:

Bạn hầu như không bao giờ sử dụng IEnumerator trực tiếp. Thông thường, bạn thực hiện tuyên bố foreach, dự kiến ​​là IEnumerable.

IEnumerable _myCollection; 
... 
foreach (var item in _myCollection) { /* Do something */ } 

Bạn cũng không vượt qua xung quanh IEnumerator. Nếu bạn muốn chuyển một bộ sưu tập cần lặp lại, bạn vượt qua IEnumerable. Vì IEnumerable có một hàm duy nhất, trả về một IEnumerator, nó có thể được sử dụng để lặp lại bộ sưu tập nhiều lần (nhiều lần).

Không cần chức năng Reset() trên IEnumerator vì nếu bạn muốn bắt đầu lại, bạn chỉ cần vứt bỏ cái cũ (thu gom rác) và lấy một cái mới.

1

Khuôn khổ .NET sẽ được hưởng lợi vô cùng nếu có phương tiện yêu cầu IEnumerator<T> về khả năng mà nó có thể hỗ trợ và những gì hứa hẹn nó có thể thực hiện. Các tính năng này cũng sẽ hữu ích trong IEnumerable<T>, nhưng có thể hỏi các câu hỏi của một điều tra viên sẽ cho phép mã có thể nhận một điều tra từ các trình bao bọc như ReadOnlyCollection để sử dụng bộ sưu tập cơ bản theo cách cải tiến mà không phải liên quan đến trình bao bọc.

Cho bất kỳ điều tra nào cho tập hợp có khả năng được liệt kê toàn bộ và không quá lớn, người ta có thể sản xuất từ ​​số IEnumerable<T> luôn mang lại cùng một chuỗi các mục (cụ thể là tập hợp các mục còn lại trong điều tra viên) bằng cách đọc toàn bộ nội dung của nó vào một mảng, xử lý và loại bỏ điều tra viên, và nhận được một điều tra viên từ mảng (sử dụng thay cho toán tử bị bỏ rơi ban đầu), gói mảng trong ReadOnlyCollection<T> và trả lại. Mặc dù cách tiếp cận như vậy sẽ làm việc với bất kỳ loại bộ sưu tập đủ điều kiện nào đáp ứng các tiêu chí trên, nó sẽ không hiệu quả khủng khiếp với hầu hết chúng. Có một phương tiện yêu cầu một điều tra để mang lại nội dung còn lại của nó trong một bất biến IEnumerable<T> sẽ cho phép nhiều loại điều tra viên thực hiện hành động được chỉ định hiệu quả hơn nhiều.

Các vấn đề liên quan