Làm cách nào để triển khai IEn Countable <T>


124

Tôi biết cách triển khai IEnumerable không chung chung, như thế này:

using System;
using System.Collections;

namespace ConsoleApplication33
{
    class Program
    {
        static void Main(string[] args)
        {
            MyObjects myObjects = new MyObjects();
            myObjects[0] = new MyObject() { Foo = "Hello", Bar = 1 };
            myObjects[1] = new MyObject() { Foo = "World", Bar = 2 };

            foreach (MyObject x in myObjects)
            {
                Console.WriteLine(x.Foo);
                Console.WriteLine(x.Bar);
            }

            Console.ReadLine();
        }
    }

    class MyObject
    {
        public string Foo { get; set; }
        public int Bar { get; set; }
    }

    class MyObjects : IEnumerable
    {
        ArrayList mylist = new ArrayList();

        public MyObject this[int index]
        {
            get { return (MyObject)mylist[index]; }
            set { mylist.Insert(index, value); }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return mylist.GetEnumerator();
        }
    }
}

Tuy nhiên tôi cũng nhận thấy rằng IEnumerable có phiên bản chung IEnumerable<T>, nhưng tôi không thể tìm ra cách triển khai nó.

Nếu tôi thêm using System.Collections.Generic;vào chỉ thị của mình bằng cách sử dụng, và sau đó thay đổi:

class MyObjects : IEnumerable

đến:

class MyObjects : IEnumerable<MyObject>

Và sau đó nhấp chuột phải vào IEnumerable<MyObject>và chọn Implement Interface => Implement Interface, Visual Studio bổ sung một cách hữu ích khối mã sau đây:

IEnumerator<MyObject> IEnumerable<MyObject>.GetEnumerator()
{
    throw new NotImplementedException();
}

Trả về đối tượng IEnumerable không chung chung từ GetEnumerator();phương thức này không hoạt động lần này, vậy tôi phải đặt gì ở đây? CLI bây giờ bỏ qua việc triển khai không chung chung và đi thẳng vào phiên bản chung khi nó cố gắng liệt kê qua mảng của tôi trong vòng lặp foreach.

Câu trả lời:


149

Nếu bạn chọn sử dụng một bộ sưu tập chung chung, chẳng hạn như List<MyObject>thay vì ArrayList, bạn sẽ thấy rằng nó List<MyObject>sẽ cung cấp cả các liệt kê chung và không chung chung mà bạn có thể sử dụng.

using System.Collections;

class MyObjects : IEnumerable<MyObject>
{
    List<MyObject> mylist = new List<MyObject>();

    public MyObject this[int index]  
    {  
        get { return mylist[index]; }  
        set { mylist.Insert(index, value); }  
    } 

    public IEnumerator<MyObject> GetEnumerator()
    {
        return mylist.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

1
Trong triển khai không chung chung, có sự khác biệt giữa trả lại this.GetEnumerator()và đơn giản là trả lại GetEnumerator()không?
Tanner Swett

1
@TannerSwett Không có sự khác biệt.
Monroe Thomas

Bạn cũng có thể thừa kế từ Collection<MyObject>đó và sau đó bạn sẽ không phải viết bất kỳ mã tùy chỉnh nào.
John Alexiou

@ ja72 Điều gì xảy ra nếu bạn đã kế thừa từ một lớp cơ sở khác và không thể kế thừa từ Bộ sưu tập <MyObject>?
Monroe Thomas

1
@MonroeThomas - sau đó bạn phải bọc List<T>và thực hiện IEnumerable<T>như trong câu trả lời của bạn.
John Alexiou

69

Bạn có thể không muốn triển khai rõ ràngIEnumerable<T> (đó là những gì bạn đã thể hiện).

Các mô hình thông thường là để sử dụng IEnumerable<T>GetEnumeratortrong việc thực hiện rõ ràng của IEnumerable:

class FooCollection : IEnumerable<Foo>, IEnumerable
{
    SomeCollection<Foo> foos;

    // Explicit for IEnumerable because weakly typed collections are Bad
    System.Collections.IEnumerator IEnumerable.GetEnumerator()
    {
        // uses the strongly typed IEnumerable<T> implementation
        return this.GetEnumerator();
    }

    // Normal implementation for IEnumerable<T>
    IEnumerator<Foo> GetEnumerator()
    {
        foreach (Foo foo in this.foos)
        {
            yield return foo;
            //nb: if SomeCollection is not strongly-typed use a cast:
            // yield return (Foo)foo;
            // Or better yet, switch to an internal collection which is
            // strongly-typed. Such as List<T> or T[], your choice.
        }

        // or, as pointed out: return this.foos.GetEnumerator();
    }
}

2
Đây là một giải pháp tốt nếu someCollection <T> chưa triển khai IEnumerable <T> hoặc nếu bạn cần chuyển đổi hoặc thực hiện xử lý khác trên mỗi phần tử trước khi nó được trả về trong chuỗi liệt kê. Nếu cả hai điều kiện này đều không đúng, thì có lẽ sẽ hiệu quả hơn khi chỉ trả về this.foos.GetEnumerator ();
Monroe Thomas

@MonroeThomas: đồng ý. Không có manh mối gì bộ sưu tập OP đang sử dụng.
dùng716

8
Điểm tốt. Nhưng thực hiện IEnumerablelà dư thừa ( IEnumerable<T>đã kế thừa từ nó).
rsenna

@rsenna điều đó đúng, tuy nhiên hãy ghi nhớ ngay cả các giao diện .NET như IList thực hiện các giao diện một cách dự phòng, đó là tính dễ đọc - giao diện công cộng IList: ICollection, IEnumerable
David Klempfner 11/12/18

@Backwards_Dave Tôi thực sự (vẫn) nghĩ rằng nó làm cho ít đọc hơn, vì nó thêm tiếng ồn không cần thiết, nhưng tôi hiểu những gì bạn nói và nghĩ rằng đó là một ý kiến ​​hợp lệ.
rsenna

24

Tại sao bạn làm nó bằng tay? yield returntự động hóa toàn bộ quá trình xử lý các vòng lặp. (Tôi cũng đã viết về nó trên blog của mình , bao gồm một cái nhìn về mã trình biên dịch được tạo ra).

Nếu bạn thực sự muốn làm điều đó một mình, bạn cũng phải trả lại một điều tra viên chung chung. Bạn sẽ không thể sử dụng ArrayListthêm nữa vì đó không phải là chung chung. Thay đổi nó để List<MyObject>thay thế. Tất nhiên điều đó giả định rằng bạn chỉ có các đối tượng thuộc loại MyObject(hoặc các loại dẫn xuất) trong bộ sưu tập của bạn.


2
+1, cần lưu ý rằng mẫu ưa thích là triển khai giao diện chung và thực hiện rõ ràng giao diện không chung chung. Lợi nhuận mang lại là giải pháp tự nhiên nhất cho giao diện chung.
dùng7116

5
Trừ khi OP sẽ thực hiện một số xử lý trong bộ liệt kê, sử dụng lợi nhuận sẽ chỉ thêm chi phí hoạt động của một máy trạng thái khác. OP chỉ nên trả về một điều tra viên được cung cấp bởi một bộ sưu tập chung cơ bản.
Monroe Thomas

@MonroeThomas: Bạn nói đúng. Tôi đã không đọc câu hỏi rất cẩn thận trước khi viết về yield return. Tôi đã sai khi cho rằng có một số xử lý tùy chỉnh.
Anders Abel

4

Nếu bạn làm việc với generic, hãy sử dụng List thay vì ArrayList. Danh sách có chính xác phương thức GetEnumerator mà bạn cần.

List<MyObject> myList = new List<MyObject>();

0

biến danh sách của tôi thành một List<MyObject>, là một lựa chọn


0

Lưu ý rằng tất cả đã được IEnumerable<T>triển khai theo System.Collectionscách tiếp cận khác là lấy MyObjectslớp của bạn từ System.Collectionslớp cơ sở ( tài liệu ):

System.Collections: Cung cấp lớp cơ sở cho một bộ sưu tập chung.

Chúng tôi sau đó có thể làm cho implemenation riêng của chúng tôi để ghi đè lên các ảo System.Collectionsphương pháp để cung cấp hành vi tùy chỉnh (chỉ dành cho ClearItems, InsertItem, RemoveItem, và SetItemcùng với Equals, GetHashCodeToStringtừ Object). Không giống như List<T>cái không được thiết kế để có thể dễ dàng mở rộng.

Thí dụ:

public class FooCollection : System.Collections<Foo>
{
    //...
    protected override void InsertItem(int index, Foo newItem)
    {
        base.InsertItem(index, newItem);     
        Console.Write("An item was successfully inserted to MyCollection!");
    }
}

public static void Main()
{
    FooCollection fooCollection = new FooCollection();
    fooCollection.Add(new Foo()); //OUTPUT: An item was successfully inserted to FooCollection!
}

Xin lưu ý rằng lái xe từ collectionchỉ được đề xuất trong trường hợp khi cần hành vi thu thập tùy chỉnh, điều này hiếm khi xảy ra. xem cách sử dụng .

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.