รับตัวแจงนับทั่วไปจากอาร์เรย์


92

ใน C # เราจะได้รับตัวแจงนับทั่วไปจากอาร์เรย์ที่กำหนดได้อย่างไร?

ในโค้ดด้านล่างนี้MyArrayคืออาร์เรย์ของMyTypeวัตถุ ฉันต้องการซื้อMyIEnumeratorตามแฟชั่นที่แสดง แต่ดูเหมือนว่าฉันจะได้รับตัวแจงนับที่ว่างเปล่า (แม้ว่าฉันจะยืนยันแล้วMyArray.Length > 0ก็ตาม)

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

ด้วยความอยากรู้อยากเห็นทำไมคุณถึงต้องการตัวแจงนับ
David Klempfner

1
@Backwards_Dave ในกรณีของฉันในสภาพแวดล้อมเธรดเดียวมีรายการไฟล์ที่แต่ละไฟล์ต้องได้รับการประมวลผลครั้งเดียวในลักษณะ Async ฉันสามารถใช้ดัชนีเพื่อเพิ่มได้ แต่การ
แจง

คำตอบ:


104

ทำงานบน 2.0+:

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

ทำงานบน 3.5+ (LINQy แฟนซีมีประสิทธิภาพน้อยกว่าเล็กน้อย):

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

3
รหัส LINQy ส่งคืนตัวแจงนับสำหรับผลลัพธ์ของวิธีการแคสต์แทนที่จะเป็นตัวนับสำหรับอาร์เรย์ ...
Guffa

1
Guffa: เนื่องจากตัวแจงนับให้การเข้าถึงแบบอ่านอย่างเดียวเท่านั้นจึงไม่แตกต่างกันมากในแง่ของการใช้งาน
Mehrdad Afshari

8
ตามที่ @Mehrdad บอกเป็นนัยว่าการใช้LINQ/Castมีลักษณะการทำงานของรันไทม์ที่ค่อนข้างแตกต่างกันเนื่องจากแต่ละองค์ประกอบของอาร์เรย์จะถูกส่งผ่านชุดการเดินทางแบบไปกลับMoveNextและตัวCurrentแจงนับพิเศษซึ่งอาจส่งผลต่อประสิทธิภาพหากอาร์เรย์มีขนาดใหญ่ ไม่ว่าในกรณีใดปัญหาสามารถหลีกเลี่ยงได้อย่างสมบูรณ์และง่ายดายโดยการได้รับตัวแจงนับที่เหมาะสมตั้งแต่แรก (กล่าวคือใช้หนึ่งในสองวิธีที่แสดงในคำตอบของฉัน)
Glenn Slayden

7
อย่างแรกคือตัวเลือกที่ดีกว่า! อย่าให้ใครมาหลอกลวงตัวเองด้วยเวอร์ชัน "แฟนซี LINQy" ซึ่งไม่จำเป็นอย่างยิ่ง
henon

@GlennSlayden ไม่เพียง แต่ช้าสำหรับอาร์เรย์ขนาดใหญ่เท่านั้น แต่ยังช้าสำหรับอาร์เรย์ทุกขนาด ดังนั้นหากคุณใช้myArray.Cast<MyType>().GetEnumerator()ในวงในสุดของคุณอาจทำให้คุณทำงานช้าลงอย่างมากแม้ในอาร์เรย์ขนาดเล็ก
Evgeniy Berezovsky

54

คุณสามารถตัดสินใจได้ด้วยตัวเองว่าการแคสต์นั้นน่าเกลียดพอที่จะรับประกันการเรียกไลบรารีภายนอกได้หรือไม่:

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

และเพื่อความสมบูรณ์หนึ่งยังควรทราบว่าต่อไปนี้ไม่ถูกต้อง - และจะผิดพลาดที่รันไทม์ - เพราะT[]เลือกที่ไม่ใช่ -generic IEnumerableอินเตอร์เฟซสำหรับเริ่มต้น (คือไม่ใช่อย่างชัดเจน) 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>
}

ความลึกลับคือเหตุใดจึงไม่SZGenericArrayEnumerator<T>สืบทอดมาจาก - SZArrayEnumeratorคลาสภายในซึ่งปัจจุบันถูกทำเครื่องหมายว่า 'ปิดผนึก' - เนื่องจากสิ่งนี้จะอนุญาตให้ตัวแจงนับทั่วไป (โควาเรียน) ถูกส่งกลับโดยค่าเริ่มต้น


มีเหตุผลไหมที่คุณใช้วงเล็บเสริม((IEnumerable<int>)arr)แต่มีวงเล็บเพียงชุดเดียว(IEnumerator<int>)arr?
David Klempfner

1
@Backwards_Dave ใช่สิ่งที่สร้างความแตกต่างระหว่างตัวอย่างที่ถูกต้องที่ด้านบนและตัวอย่างที่ไม่ถูกต้องที่ด้านล่าง ตรวจสอบลำดับความสำคัญของตัวดำเนินการC # unary "cast"
Glenn Slayden

24

เนื่องจากฉันไม่ชอบแคสต์อัปเดตเล็กน้อย:

your_array.AsEnumerable().GetEnumerator();

AsEnumerable () ยังทำการแคสต์ดังนั้นคุณจึงยังคงแคสต์อยู่ :) บางทีคุณอาจใช้ your_array.OfType <T> () .GetEnumerator ();
Mladen B.

1
ความแตกต่างคือเป็นการแคสต์โดยปริยายที่ทำในเวลาคอมไพล์แทนที่จะเป็นแคสต์ที่ชัดเจนที่รันไทม์ ดังนั้นคุณจะมีข้อผิดพลาดในการคอมไพล์มากกว่าข้อผิดพลาดรันไทม์หากประเภทไม่ถูกต้อง
Hank Schultz

@HankSchultz หากประเภทไม่ถูกต้องyour_array.AsEnumerable()จะไม่รวบรวมตั้งแต่แรกเนื่องจากAsEnumerable()สามารถใช้ได้กับอินสแตนซ์ของประเภทที่ใช้IEnumerableเท่านั้น
David Klempfner

5

เพื่อให้มันสะอาดที่สุดฉันต้องการให้คอมไพเลอร์ทำงานทั้งหมด ไม่มีการร่าย (ดังนั้นจึงปลอดภัยจริง) ไม่มีการใช้ไลบรารีของบุคคลที่สาม (System.Linq) (ไม่มีค่าใช้จ่ายรันไทม์)

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

// และใช้รหัส:

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

สิ่งนี้ใช้ประโยชน์จากเวทมนตร์ของคอมไพเลอร์ที่ช่วยให้ทุกอย่างสะอาด

อีกประเด็นที่ควรทราบคือคำตอบของฉันคือคำตอบเดียวที่จะทำการตรวจสอบเวลาคอมไพล์

สำหรับวิธีแก้ปัญหาอื่น ๆ หากประเภทของ "arr" เปลี่ยนไปการเรียกโค้ดจะคอมไพล์และล้มเหลวในขณะรันไทม์ส่งผลให้เกิดบั๊กรันไทม์

คำตอบของฉันจะทำให้โค้ดไม่สามารถคอมไพล์ได้ดังนั้นฉันจึงมีโอกาสน้อยที่จะส่งข้อบกพร่องในโค้ดของฉันเนื่องจากมันจะส่งสัญญาณให้ฉันทราบว่าฉันใช้งานผิดประเภท


การแคสต์ดังที่แสดงโดย GlennSlayden และ MehrdadAfshari ยังเป็นหลักฐานการคอมไพล์
t3chb0t

1
@ t3chb0t ไม่พวกเขาไม่ใช่ ทำงานที่รันไทม์เพราะเรารู้ว่ามีFoo[]การใช้งานIEnumerable<Foo>แต่หากมีการเปลี่ยนแปลงจะไม่ถูกตรวจพบในเวลาคอมไพล์ การแคสต์ที่ชัดเจนจะไม่มีการพิสูจน์เวลาคอมไพล์ แทนที่จะกำหนด / ส่งคืนอาร์เรย์เป็น IEnumerable <Foo> ใช้การส่งโดยนัยซึ่งเป็นหลักฐานเวลาคอมไพล์
Toxantron

@Toxantron การแคสต์อย่างชัดเจนไปยัง IEnumerable <T> จะไม่คอมไพล์เว้นแต่ว่าประเภทที่คุณพยายามแคสต์จะใช้ IEnumerable เมื่อคุณพูดว่า "แต่ถ้าสิ่งนั้นเปลี่ยนแปลงไป" คุณช่วยยกตัวอย่างความหมายได้ไหม
David Klempfner

แน่นอนมันรวบรวม นั่นคือสิ่งที่InvalidCastExceptionมีไว้สำหรับ คอมไพเลอร์ของคุณอาจเตือนคุณว่าแคสต์บางตัวไม่ทำงาน ลองvar foo = (int)new object(). คอมไพล์ได้ดีและขัดข้องที่รันไทม์
Toxantron

2

YourArray.OfType (). GetEnumerator ();

อาจทำงานได้ดีขึ้นเล็กน้อยเนื่องจากต้องตรวจสอบประเภทเท่านั้นไม่ใช่แคสต์


คุณต้องระบุประเภทอย่างชัดเจนเมื่อใช้OfType<..type..>()- อย่างน้อยในกรณีของฉันคือdouble[][]
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)
    {

    }

คุณช่วยอธิบายได้ไหมว่าโค้ดข้างบนนี้ใช้งานได้อย่างไร>
Phani

น่าโหวตกว่านี้เยอะ! การใช้คอมไพเลอร์โดยนัยเป็นวิธีแก้ปัญหาที่สะอาดและง่ายที่สุด
Toxantron

-1

แน่นอนว่าสิ่งที่คุณทำได้มีเพียงแค่ใช้ตัวแจงนับทั่วไปของคุณเองสำหรับอาร์เรย์

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. */}
    }
}

นี่มากหรือน้อยเท่ากับการใช้. NET ของ SZGenericArrayEnumerator <T> ตามที่กล่าวโดย Glenn Slayden แน่นอนคุณควรทำสิ่งนี้เท่านั้นเป็นกรณีที่คุ้มค่ากับความพยายาม ในกรณีส่วนใหญ่จะไม่เป็นเช่นนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.