lấy bảng liệt kê chung từ một mảng


91

Trong C #, làm cách nào để có được một kiểu liệt kê chung từ một mảng nhất định?

Trong đoạn mã dưới đây, MyArraylà một mảng các MyTypeđối tượng. Tôi muốn lấy MyIEnumeratortheo thời trang được hiển thị, nhưng có vẻ như tôi có được một điều tra viên trống (mặc dù tôi đã xác nhận điều đó MyArray.Length > 0).

MyType[] MyArray = ... ;
IEnumerator<MyType> MyIEnumerator = MyArray.GetEnumerator() as IEnumerator<MyType>;

Chỉ vì tò mò, tại sao bạn muốn có được điều tra viên?
David Klempfner

1
@Backwards_Dave trong trường hợp của tôi trong môi trường một luồng có một danh sách các tệp mà mỗi tệp phải được xử lý một lần, theo cách Không đồng bộ. Tôi có thể sử dụng một chỉ số tăng, nhưng enumerables là mát :)
hpaknia

Câu trả lời:


102

Hoạt động trên 2.0+:

((IEnumerable<MyType>)myArray).GetEnumerator()

Hoạt động trên 3.5+ (LINQy ưa thích, kém hiệu quả hơn một chút):

myArray.Cast<MyType>().GetEnumerator()   // returns IEnumerator<MyType>

3
Mã LINQy thực sự trả về một Enumerator cho kết quả của phương pháp Cast, chứ không phải là một điều tra viên cho mảng ...
Guffa

1
Guffa: vì điều tra viên chỉ cung cấp quyền truy cập chỉ đọc nên nó không có sự khác biệt lớn về cách sử dụng.
Mehrdad Afshari

8
Như @Mehrdad ngụ ý, Sử dụng LINQ/Castcó hành vi thời gian chạy khá khác nhau, vì mỗi và mọi phần tử của mảng sẽ được chuyển qua một tập hợp bổ sung MoveNextCurrentđiều tra viên, và điều này có thể ảnh hưởng đến hiệu suất nếu mảng lớn. Trong mọi trường hợp, vấn đề hoàn toàn và dễ dàng tránh được bằng cách tìm được người điều tra thích hợp ngay từ đầu (tức là sử dụng một trong hai phương pháp được trình bày trong câu trả lời của tôi).
Glenn Slayden

7
Đầu tiên là lựa chọn tốt hơn! Đừng để ai đó tự lừa dối mình bởi phiên bản "LINQy ưa thích" hoàn toàn không cần thiết.
henon

@GlennSlayden Nó không chỉ chậm đối với các mảng lớn mà còn chậm đối với bất kỳ kích thước mảng nào. Vì vậy, nếu bạn sử dụng myArray.Cast<MyType>().GetEnumerator()trong vòng lặp trong cùng của mình, nó có thể làm chậm bạn đáng kể ngay cả đối với các mảng nhỏ.
Evgeniy Berezovsky

54

Bạn có thể tự quyết định xem quá trình truyền có đủ xấu để đảm bảo một lệnh gọi thư viện không liên quan hay không:

int[] arr;
IEnumerator<int> Get1()
{
    return ((IEnumerable<int>)arr).GetEnumerator();  // <-- 1 non-local call

    // ldarg.0 
    // ldfld int32[] foo::arr
    // castclass System.Collections.Generic.IEnumerable`1<int32>
    // callvirt instance class System.Collections.Generic.IEnumerator`1<!0> System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
}

IEnumerator<int> Get2()
{
    return arr.AsEnumerable().GetEnumerator();   // <-- 2 non-local calls

    // ldarg.0 
    // ldfld int32[] foo::arr
    // call class System.Collections.Generic.IEnumerable`1<!!0> System.Linq.Enumerable::AsEnumerable<int32>(class System.Collections.Generic.IEnumerable`1<!!0>)
    // callvirt instance class System.Collections.Generic.IEnumerator`1<!0> System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
}

Và cho đầy đủ, người ta cũng nên lưu ý rằng những điều sau đây là không đúng - và sẽ sụp đổ trong thời gian chạy - vì T[]chọn không -generic IEnumerablegiao diện cho mặc định của nó (tức là không rõ ràng) thực hiện GetEnumerator().

IEnumerator<int> NoGet()                    // error - do not use
{
    return (IEnumerator<int>)arr.GetEnumerator();

    // ldarg.0 
    // ldfld int32[] foo::arr
    // callvirt instance class System.Collections.IEnumerator System.Array::GetEnumerator()
    // castclass System.Collections.Generic.IEnumerator`1<int32>
}

Bí ẩn là, tại sao không SZGenericArrayEnumerator<T>kế thừa từ SZArrayEnumerator--an lớp bên trong hiện được đánh dấu là 'niêm phong' - vì điều này sẽ cho phép trả về kiểu liệt kê chung (đồng biến) theo mặc định?


Có lý do tại sao bạn sử dụng thêm dấu ngoặc trong ((IEnumerable<int>)arr)nhưng chỉ có một bộ dấu ngoặc trong (IEnumerator<int>)arr?
David Klempfner

1
@Backwards_Dave Có, đó là điều tạo nên sự khác biệt giữa ví dụ chính xác ở trên cùng và ví dụ không chính xác ở dưới cùng. Kiểm tra ưu tiên toán tử của toán tử "ép kiểu" một ngôi C # .
Glenn Slayden

24

Vì tôi không thích truyền, nên cập nhật một chút:

your_array.AsEnumerable().GetEnumerator();

AsEnumerable () cũng thực hiện ép kiểu, vì vậy bạn vẫn đang thực hiện ép kiểu :) Có thể bạn có thể sử dụng your_array.OfType <T> () .GetEnumerator ();
Mladen B.

1
Sự khác biệt là đó là một kiểu truyền ngầm được thực hiện tại thời điểm biên dịch chứ không phải là một kiểu ép kiểu rõ ràng được thực hiện trong thời gian chạy. Do đó, bạn sẽ gặp lỗi thời gian biên dịch chứ không phải lỗi thời gian chạy nếu kiểu sai.
Hank Schultz

@HankSchultz nếu kiểu sai thì your_array.AsEnumerable()sẽ không biên dịch ngay từ đầu vì AsEnumerable()chỉ có thể được sử dụng trên các trường hợp của kiểu triển khai IEnumerable.
David Klempfner

5

Để làm cho nó sạch nhất có thể, tôi muốn để trình biên dịch thực hiện tất cả công việc. Không có phôi (vì vậy nó thực sự là loại an toàn). Không sử dụng Thư viện của bên thứ ba (System.Linq) (Không có chi phí thời gian chạy).

    public static IEnumerable<T> GetEnumerable<T>(this T[] arr)
    {
        return arr;
    }

// Và để sử dụng mã:

    String[] arr = new String[0];
    arr.GetEnumerable().GetEnumerator()

Điều này tận dụng một số phép thuật của trình biên dịch giúp giữ mọi thứ sạch sẽ.

Điểm khác cần lưu ý là câu trả lời của tôi là câu trả lời duy nhất sẽ kiểm tra thời gian biên dịch.

Đối với bất kỳ giải pháp nào khác nếu kiểu "arr" thay đổi, thì mã gọi sẽ biên dịch và không thành công trong thời gian chạy, dẫn đến lỗi thời gian chạy.

Câu trả lời của tôi sẽ khiến mã không được biên dịch và do đó tôi ít có cơ hội gửi lỗi trong mã của mình hơn, vì nó sẽ báo hiệu cho tôi biết rằng tôi đang sử dụng sai loại.


Truyền như được hiển thị bởi GlennSlayden và MehrdadAfshari cũng là bằng chứng thời gian biên dịch.
t3chb0t

1
@ t3chb0t không họ không phải vậy. Công việc trong thời gian chạy bởi vì chúng tôi biết rằng điều đó Foo[]thực hiện IEnumerable<Foo>, nhưng nếu điều đó thay đổi, nó sẽ không được phát hiện tại thời điểm biên dịch. Các phôi rõ ràng không bao giờ là bằng chứng thời gian biên dịch. Thay vào đó, gán / trả về mảng dưới dạng IEnumerable <Foo> sử dụng kết hợp ngầm định là bằng chứng thời gian biên dịch.
Toxantron

@Toxantron Một kiểu ép kiểu rõ ràng cho IEnumerable <T> sẽ không được biên dịch trừ khi kiểu bạn đang cố ép kiểu thực hiện IEnumerable. Khi bạn nói "nhưng nếu điều đó luôn thay đổi", bạn có thể cung cấp ví dụ về ý của bạn không?
David Klempfner

Tất nhiên nó biên dịch. Đó là những gì InvalidCastException dành cho. Trình biên dịch của bạn có thể cảnh báo bạn rằng một bộ truyền nào đó không hoạt động. Cố gắng var foo = (int)new object(). Nó biên dịch tốt và gặp sự cố trong thời gian chạy.
Toxantron

2

YourArray.OfType (). GetEnumerator ();

có thể hoạt động tốt hơn một chút, vì nó chỉ phải kiểm tra loại chứ không phải ép kiểu.


Bạn cần phải xác định rõ kiểu dữ liệu khi sử dụng OfType<..type..>()- ít nhất là trong trường hợp của tôidouble[][]
M. Mimpen

0
    MyType[] arr = { new MyType(), new MyType(), new MyType() };

    IEnumerable<MyType> enumerable = arr;

    IEnumerator<MyType> en = enumerable.GetEnumerator();

    foreach (MyType item in enumerable)
    {

    }

Bạn có thể vui lòng giải thích những gì ở trên mã sẽ thực hiện>
Phani

Điều này sẽ được bỏ phiếu cao hơn nhiều! Sử dụng diễn viên ngầm của trình biên dịch là giải pháp rõ ràng và dễ dàng nhất.
Toxantron

-1

Tất nhiên, những gì bạn có thể làm chỉ là triển khai kiểu liệt kê chung của riêng bạn cho các mảng.

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

namespace SomeNamespace
{
    public class ArrayEnumerator<T> : IEnumerator<T>
    {
        public ArrayEnumerator(T[] arr)
        {
            collection = arr;
            length = arr.Length;
        }
        private readonly T[] collection;
        private int index = -1;
        private readonly int length;

        public T Current { get { return collection[index]; } }

        object IEnumerator.Current { get { return Current; } }

        public bool MoveNext() { index++; return index < length; }

        public void Reset() { index = -1; }

        public void Dispose() {/* Nothing to dispose. */}
    }
}

Điều này ít nhiều tương đương với việc nhập .NET của SZGenericArrayEnumerator <T> như Glenn Slayden đã đề cập. Tất nhiên bạn chỉ nên làm điều này, đây là những trường hợp đáng để nỗ lực. Trong hầu hết các trường hợp, nó không phải là.

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.