ทุกคนสามารถอธิบาย IEnumerable และ IEnumerator ให้ฉันได้ไหม [ปิด]


272

ทุกคนสามารถอธิบาย IEnumerable และ IEnumerator ให้ฉันได้ไหม

ตัวอย่างเช่นเมื่อใดที่จะใช้งานเกิน foreach ความแตกต่างระหว่าง IEnumerable และ IEnumerator คืออะไร ทำไมเราต้องใช้มัน?


55
นี่คือการผสมผสานการตั้งชื่อที่เลวร้ายที่สุดตั้งแต่ Java และ JavaScript
Matthew Lock

@ MatthewLock คุณสามารถอธิบายสิ่งที่คุณหมายถึงอะไร?
David Klempfner

คำตอบ:


274

ตัวอย่างเช่นเมื่อใดที่จะใช้งานเกิน foreach

คุณไม่ได้ใช้"มากกว่า"IEnumerable foreachการดำเนินการIEnumerableจะทำให้การใช้ที่เป็นไปได้foreach

เมื่อคุณเขียนโค้ดเช่น:

foreach (Foo bar in baz)
{
   ...
}

มันเทียบเท่ากับการเขียนตามหน้าที่:

IEnumerator bat = baz.GetEnumerator();
while (bat.MoveNext())
{
   bar = (Foo)bat.Current
   ...
}

โดย "การทำงานเทียบเท่า" ฉันหมายความว่านั่นคือสิ่งที่คอมไพเลอร์เปลี่ยนรหัสเป็น คุณไม่สามารถใช้foreachกับbazตัวอย่างนี้ได้เว้นแต่จะ bazดำเนินการIEnumerableการดำเนินการ

IEnumerableหมายความว่าbazใช้วิธีการ

IEnumerator GetEnumerator()

IEnumeratorวัตถุว่าวิธีนี้ผลตอบแทนที่ต้องใช้วิธีการที่

bool MoveNext()

และ

Object Current()

วิธีแรกเลื่อนไปยังวัตถุถัดไปในIEnumerableวัตถุที่สร้างตัวแจงนับกลับมาfalseถ้ามันเสร็จแล้วและที่สองส่งคืนวัตถุปัจจุบัน

IEnumerableอะไรในสุทธิที่คุณสามารถย้ำกว่าการดำเนินการ หากคุณกำลังสร้างชั้นเรียนของคุณเองและมันไม่ได้สืบทอดมาจากคลาสที่ดำเนินการอยู่IEnumerableคุณสามารถทำให้ชั้นเรียนของคุณสามารถใช้งานได้ในการforeachแถลงโดยการนำไปใช้IEnumerable(และโดยการสร้างคลาสตัวแจงนับว่าGetEnumeratorวิธีการใหม่จะส่งกลับ)


15
ฉันคิดว่าเมื่อผู้โพสต์ดั้งเดิมพูดว่า "over foreach" เขาหมายถึง "เมื่อใดฉันจึงควรเรียก GetEnumerator () / MoveNext () อย่างชัดเจนแทนที่จะใช้ foreach loop" สำหรับสิ่งที่คุ้มค่า
mqp

6
อ่าฉันคิดว่าคุณพูดถูก คำตอบโดยนัยในโพสต์ของฉัน: มันไม่สำคัญเพราะนั่นคือสิ่งที่ผู้แปลทำอยู่แล้ว ทำสิ่งที่ง่ายที่สุดเช่นใช้ foreach
Robert Rossney

12
คำตอบนี้ไม่ได้บอกเรื่องราวทั้งหมด วัตถุนับไม่จำเป็นต้องใช้ IEnumerable; พวกเขาจำเป็นต้องมีเมธอด GetEnumerator ที่ส่งคืนอินสแตนซ์ของชนิดที่มีbool MoveNext()เมธอดและCurrentคุณสมบัติใด ๆ วิธีการ "พิมพ์เป็ด" นี้ถูกนำมาใช้ใน C # 1.0 เพื่อหลีกเลี่ยงการชกมวยเมื่อระบุคอลเลกชันประเภทค่า
phoog

3
ด้วย exlanation จากด้านบนและทำงานผ่านWorkthrough: การใช้ IEnumerable ของ (T)คุณจะมีแหล่งที่ดี
ruedi

4
เป็นเช่น C ++ iteratorหรือไม่
Matt G

161

อินเทอร์เฟซ IEnumerable และ IEnumerator

ในการเริ่มตรวจสอบกระบวนการของการใช้. NET อินเตอร์เฟสที่มีอยู่ก่อนอื่นมาดูบทบาทของ IEnumerable และ IEnumerator จำได้ว่า C # รองรับคีย์เวิร์ดชื่อ foreach ที่อนุญาตให้คุณทำซ้ำเนื้อหาของอาเรย์ประเภทใดก็ได้:

// Iterate over an array of items.
int[] myArrayOfInts = {10, 20, 30, 40};
foreach(int i in myArrayOfInts)
{
   Console.WriteLine(i);
}

ในขณะที่มันอาจดูเหมือนว่ามีเพียงประเภทอาเรย์เท่านั้นที่สามารถใช้ประโยชน์จากการสร้างนี้ความจริงของเรื่องนี้คือประเภทใด ๆ ที่สนับสนุนวิธีการที่ชื่อว่า

สมมติว่าเรามีคลาสโรงรถ

// Garage contains a set of Car objects.
public class Garage
{
   private Car[] carArray = new Car[4];
   // Fill with some Car objects upon startup.
   public Garage()
   {
      carArray[0] = new Car("Rusty", 30);
      carArray[1] = new Car("Clunker", 55);
      carArray[2] = new Car("Zippy", 30);
      carArray[3] = new Car("Fred", 30);
   }
}

เป็นการดีที่จะสะดวกกว่าที่จะวนซ้ำไอเท็มย่อยของวัตถุ Garage โดยใช้การสร้าง foreach เหมือนกับอาร์เรย์ของค่าข้อมูล:

// This seems reasonable ...
public class Program
{
   static void Main(string[] args)
   {
      Console.WriteLine("***** Fun with IEnumerable / IEnumerator *****\n");
      Garage carLot = new Garage();
      // Hand over each car in the collection?
      foreach (Car c in carLot)
      {
         Console.WriteLine("{0} is going {1} MPH",
         c.PetName, c.CurrentSpeed);
      }
      Console.ReadLine();
   }
}

น่าเศร้าที่คอมไพเลอร์แจ้งให้คุณทราบว่าคลาส Garage ไม่ได้ใช้วิธีการที่ชื่อว่า GetEnumerator () วิธีนี้ถูกทำให้เป็นระเบียบโดยอินเตอร์เฟส IEnumerable ซึ่งพบที่ซุ่มซ่อนอยู่ใน System.Collections namespace คลาสหรือโครงสร้างที่สนับสนุนลักษณะการทำงานนี้โฆษณาว่าพวกเขาสามารถแสดงรายการย่อยที่มีการเรียก (ในตัวอย่างนี้คำสำคัญ foreach เอง) นี่คือคำจำกัดความของอินเตอร์เฟส. NET มาตรฐานนี้:

// This interface informs the caller
// that the object's subitems can be enumerated.
public interface IEnumerable
{
   IEnumerator GetEnumerator();
}

อย่างที่คุณเห็นเมธอด GetEnumerator () จะส่งคืนการอ้างอิงไปยังอินเตอร์เฟสอื่นที่ชื่อ System.Collections.IEnumerator อินเทอร์เฟซนี้มีโครงสร้างพื้นฐานเพื่ออนุญาตให้ผู้เรียกผ่านภายในวัตถุที่อยู่ในคอนเทนเนอร์ที่เข้ากันได้กับ IEnumerable:

// This interface allows the caller to
// obtain a container's subitems.
public interface IEnumerator
{
   bool MoveNext (); // Advance the internal position of the cursor.
   object Current { get;} // Get the current item (read-only property).
   void Reset (); // Reset the cursor before the first member.
}

หากคุณต้องการอัปเดตประเภทโรงรถเพื่อรองรับอินเทอร์เฟซเหล่านี้คุณสามารถใช้ถนนยาวและใช้แต่ละวิธีด้วยตนเอง ในขณะที่คุณมีอิสระที่จะจัดหา GetEnumerator () รุ่นที่กำหนดเอง, MoveNext (), กระแสและรีเซ็ต () ที่กำหนดเอง แต่ก็มีวิธีที่ง่ายกว่า เนื่องจากชนิด System.Array (รวมถึงคลาสคอลเลกชันอื่น ๆ อีกมากมาย) ได้ใช้ IEnumerable และ IEnumerator แล้วคุณสามารถมอบหมายการร้องขอให้กับ System.Array ดังนี้

using System.Collections;
...
public class Garage : IEnumerable
{
   // System.Array already implements IEnumerator!
   private Car[] carArray = new Car[4];
   public Garage()
   {
      carArray[0] = new Car("FeeFee", 200);
      carArray[1] = new Car("Clunker", 90);
      carArray[2] = new Car("Zippy", 30);
      carArray[3] = new Car("Fred", 30);
   }
   public IEnumerator GetEnumerator()
   {
      // Return the array object's IEnumerator.
      return carArray.GetEnumerator();
   }
}

หลังจากคุณอัปเดตประเภทโรงรถของคุณแล้วคุณสามารถใช้ประเภทภายในโครงสร้าง C # foreach ได้อย่างปลอดภัย นอกจากนี้เนื่องจากการกำหนด GetEnumerator () วิธีการเปิดเผยต่อสาธารณชนผู้ใช้วัตถุสามารถโต้ตอบกับประเภท IEnumerator:

// Manually work with IEnumerator.
IEnumerator i = carLot.GetEnumerator();
i.MoveNext();
Car myCar = (Car)i.Current;
Console.WriteLine("{0} is going {1} MPH", myCar.PetName, myCar.CurrentSpeed);

อย่างไรก็ตามหากคุณต้องการซ่อนฟังก์ชันการทำงานของ IEnumerable จากระดับวัตถุให้ใช้ประโยชน์จากการใช้อินเตอร์เฟสอย่างชัดเจน:

IEnumerator IEnumerable.GetEnumerator()
{
  // Return the array object's IEnumerator.
  return carArray.GetEnumerator();
}

โดยการทำเช่นนั้นผู้ใช้วัตถุทั่วไปจะไม่พบเมธอด GetEnumerator () ของ Garage ในขณะที่โครงสร้าง foreach จะได้รับส่วนต่อประสานในพื้นหลังเมื่อจำเป็น

ดัดแปลงจากPro C # 5.0 และ. NET 4.5 Framework


1
คำตอบที่ยอดเยี่ยมขอบคุณ! ฉันมีคำถามหนึ่งข้อ ในตัวอย่างแรกคุณวนลูปมากกว่าอาร์เรย์โดยใช้ foreach แต่คุณไม่สามารถทำได้ในตัวอย่างที่สอง เป็นเพราะอาร์เรย์อยู่ในคลาสหรือมีวัตถุหรือไม่
Caleb Palmquist

ขอบคุณสำหรับคำอธิบายที่ดี คำถามเดียวของฉันคือทำไมไม่เพียง แต่มีวิธีการGarageที่ได้รับcarArray? ด้วยวิธีนี้คุณไม่จำเป็นต้องดำเนินการGetEnumeratorด้วยตัวเองตั้งแต่Arrayนี้มาแล้ว ตัวอย่างforeach(Car c in carLot.getCars()) { ... }
Nick Rolando

63

การใช้ IEnumerable หมายถึงคลาสของคุณส่งคืนออบเจ็กต์ IEnumerator:

public class People : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator()
    {
        // return a PeopleEnumerator
    }
}

การใช้ IEnumerator หมายถึงคลาสของคุณส่งคืนเมธอดและคุณสมบัติสำหรับการวนซ้ำ:

public class PeopleEnumerator : IEnumerator
{
    public void Reset()...

    public bool MoveNext()...

    public object Current...
}

นั่นคือความแตกต่างอยู่แล้ว


1
ดีนี่อธิบาย (พร้อมกับโพสต์อื่น ๆ ) วิธีการแปลงชั้นเรียนของคุณเป็นหนึ่งที่คุณสามารถใช้ "foreach" เพื่อย้ำเนื้อหาของมัน
Contango

54

คำอธิบายผ่านคำแนะนำแบบอะนาล็อก + รหัส

ก่อนอื่นคำอธิบายที่ไม่มีรหัสจากนั้นฉันจะเพิ่มในภายหลัง

สมมติว่าคุณเป็น บริษัท สายการบิน และในเครื่องบินแต่ละลำคุณต้องการทราบข้อมูลเกี่ยวกับผู้โดยสารที่บินอยู่บนเครื่องบิน โดยทั่วไปคุณต้องการที่จะ "สำรวจ" เครื่องบิน กล่าวอีกนัยหนึ่งคุณต้องการที่จะสามารถเริ่มต้นที่ที่นั่งด้านหน้าและจากนั้นทำงานไปทางด้านหลังของเครื่องบินถามข้อมูลผู้โดยสาร: พวกเขาเป็นใครมาจากไหน ฯลฯ เครื่องบินสามารถทำสิ่งนี้ได้ , ถ้ามันเป็น:

  1. นับได้และ
  2. ถ้ามีเคาน์เตอร์

ทำไมต้องมีข้อกำหนดเหล่านี้เพราะนั่นคือสิ่งที่อินเตอร์เฟซต้องการ

หากนี่คือข้อมูลที่เกินพิกัดสิ่งที่คุณต้องรู้ก็คือคุณต้องการถามคำถามผู้โดยสารแต่ละคนบนเครื่องบินโดยเริ่มจากคำถามแรกและทำในสิ่งสุดท้าย

สิ่งที่นับได้หมายถึงอะไร

หากสายการบิน "นับได้" หมายความว่าจะต้องมีพนักงานต้อนรับบนเครื่องบินที่มีหน้าที่นับ แต่เพียงผู้เดียว - และผู้ดูแลเที่ยวบินนี้ต้องนับอย่างเฉพาะเจาะจง:

  1. ผู้ดูแลเคาน์เตอร์ / เที่ยวบินจะต้องเริ่มต้นก่อนผู้โดยสารคนแรก (ที่ด้านหน้าของทุกคนที่พวกเขาสาธิตความปลอดภัยวิธีการใส่เสื้อชูชีพ ฯลฯ )
  2. เขา / เธอ (เช่นพนักงานต้อนรับบนเครื่องบิน) ต้อง "เดินต่อไป" ขึ้นทางเดินไปยังที่นั่งแรก
  3. เขา / เธอจะต้องบันทึก: (i) บุคคลนั้นอยู่ในที่นั่งและ (ii) ตำแหน่งปัจจุบันของพวกเขาในทางเดิน

ขั้นตอนการนับ

กัปตันของสายการบินต้องการรายงานเกี่ยวกับผู้โดยสารทุกคนและเมื่อพวกเขาถูกสอบสวนหรือนับ ดังนั้นหลังจากพูดคุยกับบุคคลที่อยู่ในที่นั่งแรกพนักงานต้อนรับบนเครื่องบิน / เคาน์เตอร์แล้วรายงานต่อกัปตันและเมื่อได้รับรายงานแล้วเคาน์เตอร์จะจดจำตำแหน่งที่แน่นอนของเขา / เธอในทางเดินและนับต่อไปเรื่อย ๆ ปิด

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

  • เคาน์เตอร์ยังคงดำเนินต่อไปจนกว่าเขาจะถึงจุดสิ้นสุดของเครื่องบิน

มาผูกสิ่งนี้กับ IEnumerables

  • นับเป็นเพียงชุดของผู้โดยสารบนเครื่องบิน กฎหมายการบินพลเรือน - กฎเหล่านี้เป็นกฎที่ IEnumerables ทั้งหมดต้องปฏิบัติตาม ทุกครั้งที่ผู้ดูแลสายการบินไปหากัปตันพร้อมกับข้อมูลผู้ให้พาสเวิร์ดโดยทั่วไปเราจะ“ ยอม” ผู้โดยสารให้กับกัปตัน กัปตันสามารถทำสิ่งใดก็ได้ตามต้องการกับผู้โดยสารโดยทั่วไปยกเว้นการจัดเรียงผู้โดยสารบนเครื่องบิน ในกรณีนี้พวกเขาจะได้รับการดูแลเป็นพิเศษหากพวกเขาทำตามแมนเชสเตอร์ซิตี้ (ugh!)

    foreach (Passenger passenger in Plane)
    // the airline hostess is now at the front of the plane
    // and slowly making her way towards the back
    // when she get to a particular passenger she gets some information
    // about the passenger and then immediately heads to the cabin
    // to let the captain decide what to do with it
    { // <---------- Note the curly bracket that is here.
        // we are now cockpit of the plane with the captain.
        // the captain wants to give the passenger free 
        // champaign if they support manchester city
        if (passenger.supports_mancestercity())
        {
            passenger.getFreeChampaign();
        } else
        {
            // you get nothing! GOOD DAY SIR!
        }
    } //  <---- Note the curly bracket that is here!
          the hostess has delivered the information 
          to the captain and goes to the next person
          on the plane (if she has not reached the 
          end of the plane)

สรุป

ในคำอื่น ๆ สิ่งที่นับว่ามันมีเคาน์เตอร์ และตัวนับต้อง (โดยพื้นฐาน): (i) จดจำสถานที่ ( สถานะ ), (ii) สามารถเคลื่อนที่ได้ต่อไป (iii) และรู้เกี่ยวกับกระแสบุคคลที่เขาติดต่อด้วย

การนับได้เป็นเพียงคำแฟนซีสำหรับ "นับได้" กล่าวอีกนัยหนึ่งการนับจำนวนช่วยให้คุณสามารถ 'แจกแจง' (เช่นการนับ)


23

IEnumerableใช้ GetEnumerator เมื่อเรียกใช้เมธอดนั้นจะส่งคืนIEnumeratorซึ่งใช้ MoveNext, Reset และ Current

ดังนั้นเมื่อคลาสของคุณใช้ IEnumerable คุณกำลังบอกว่าคุณสามารถเรียกเมธอด (GetEnumerator) และรับค่าออบเจ็กต์ใหม่ที่ส่งคืน (IEnumerator) ที่คุณสามารถใช้ในลูปเช่น foreach


18

การใช้ IEnumerable ช่วยให้คุณรับ IEnumerator สำหรับรายการ

IEnumerator อนุญาตให้เข้าถึงลำดับของลักษณะรายการก่อนหน้าในรายการโดยใช้คีย์เวิร์ด yield

ก่อน foreach การนำไปใช้งาน (ใน Java 1.4 เป็นต้น) วิธีการย้ำรายการคือการรับตัวแจงนับจากรายการจากนั้นขอให้รายการ "ถัดไป" ในรายการตราบเท่าที่ค่าที่ส่งคืนเป็นถัดไป รายการไม่เป็นโมฆะ Foreach ทำเช่นนั้นโดยนัยว่าเป็นคุณสมบัติภาษาในลักษณะเดียวกับที่ lock () ใช้คลาส Monitor ที่อยู่เบื้องหลัง

ฉันคาดว่า foreach สามารถทำงานกับรายการได้เนื่องจากใช้ IEnumerable


หากเป็นรายการฉันจะใช้ foreach ไม่ได้ทำไม
prodev42

.Net Duck พิมพ์คำสั่ง foreach แต่ IEnumerable / IEnumerable <T> เป็นวิธีที่เหมาะสมในการบอกว่าคลาสของคุณสามารถระบุได้
user7116

15
  • วัตถุที่ใช้IEnumerableอนุญาตให้ผู้อื่นเยี่ยมชมแต่ละรายการ(โดยตัวระบุ) (โดยการแจงนับ)
  • วัตถุที่ใช้IEnumeratorคือการทำซ้ำ มันวนลูปมากกว่าวัตถุที่นับได้

คิดว่าวัตถุที่แจกแจงเป็นของรายการกองต้นไม้


12

IEnumerable และ IEnumerator (และ counterparts ทั่วไปของพวกเขา IEnumerable <T> และ IEnumerator <T>) จะมีการเชื่อมต่อฐานของiteratorการใช้งานในคอลเลกชันสุทธิกรอบชั้น libray

IEnumerableเป็นอินเทอร์เฟซที่พบบ่อยที่สุดที่คุณเห็นในโค้ดส่วนใหญ่ มันเปิดใช้งาน foreach loop เครื่องปั่นไฟ (คิดว่าให้ผลผลิต ) และเนื่องจากส่วนต่อประสานขนาดเล็กของมันจึงถูกใช้เพื่อสร้าง abstractions ที่แน่น IEnumerable ขึ้นอยู่กับ IEnumeratorIEnumerator

ในทางกลับกันIEnumeratorจัดเตรียมอินเตอร์เฟสการทำซ้ำระดับที่ต่ำกว่าเล็กน้อย มันเรียกว่าiterator อย่างชัดเจนซึ่งทำให้โปรแกรมเมอร์สามารถควบคุมวงจรการวนซ้ำได้มากขึ้น

IEnumerable

IEnumerable เป็นอินเทอร์เฟซมาตรฐานที่เปิดใช้งานการวนซ้ำคอลเลกชันที่สนับสนุน (อันที่จริงแล้วคอลเลกชันทุกประเภทที่ฉันคิดได้ในวันนี้จะใช้IEnumerable ) foreachสนับสนุนคอมไพเลอร์จะช่วยให้คุณลักษณะของภาษาเช่น ในแง่ทั่วไปมันนี้จะช่วยให้การดำเนินงาน iterator โดยปริยาย

foreach Loop

foreach (var value in list)
  Console.WriteLine(value);

ฉันคิดว่าการforeachวนซ้ำเป็นหนึ่งในสาเหตุหลักของการใช้อินเทอร์เฟซIEnumerable foreachมีไวยากรณ์ที่รวบรัดมากและเข้าใจง่ายมากเมื่อเทียบกับ Classic Cสไตล์แบบสำหรับลูปที่คุณต้องตรวจสอบตัวแปรต่างๆเพื่อดูว่ามันทำอะไร

ผลผลิตคำหลัก

อาจเป็นคุณสมบัติที่รู้จักกันน้อยกว่าคือIEnumerableยังช่วยให้เครื่องกำเนิดไฟฟ้าใน C #ด้วยการใช้yield returnและyield breakคำสั่ง

IEnumerable<Thing> GetThings() {
   if (isNotReady) yield break;
   while (thereIsMore)
     yield return GetOneMoreThing();
}

นามธรรม

สถานการณ์ทั่วไปอีกข้อหนึ่งในทางปฏิบัติคือการใช้IEnumerableเพื่อให้เป็นนามธรรมแบบเรียบง่าย เนื่องจากเป็นอินเทอร์เฟซจิ๋วและอ่านอย่างเดียวคุณควรแสดงคอลเลกชันของคุณเป็นIEnumerable (แทนที่จะเป็นListตัวอย่าง) วิธีนี้ทำให้คุณมีอิสระในการเปลี่ยนแปลงการใช้งานโดยไม่ทำให้รหัสของลูกค้าของคุณพัง (เปลี่ยนรายการเป็นLinkedListเป็นต้น)

Gotcha

พฤติกรรมหนึ่งที่ต้องระวังคือในการใช้งานแบบสตรีมมิ่ง (เช่นการดึงข้อมูลแถวต่อแถวจากฐานข้อมูลแทนที่จะโหลดผลลัพธ์ทั้งหมดในหน่วยความจำก่อน) คุณไม่สามารถวนซ้ำการรวบรวมมากกว่าหนึ่งครั้ง ตรงกันข้ามกับคอลเลกชันในหน่วยความจำเช่นรายการซึ่งคุณสามารถทำซ้ำได้หลายครั้งโดยไม่มีปัญหา ตัวอย่างเช่น ReSharper มีการตรวจสอบรหัสสำหรับการแจกแจงจำนวนเต็มที่เป็นไปได้ของ IEnumerable

IEnumerator

ในทางกลับกัน IEnumerator เป็นอินเทอร์เฟซเบื้องหลังซึ่งทำให้IEnumerble-foreach-magicทำงาน พูดอย่างเคร่งครัดมันเปิดใช้งานตัวทำซ้ำอย่างชัดเจน

var iter = list.GetEnumerator();
while (iter.MoveNext())
    Console.WriteLine(iter.Current);

จากประสบการณ์ของฉันIEnumeratorไม่ค่อยได้ใช้ในสถานการณ์ทั่วไปเนื่องจากมีรูปแบบ verbose มากกว่าและมี semantics ที่สับสนเล็กน้อย (อย่างน้อยสำหรับฉันเช่นMoveNext ()ส่งคืนค่าเช่นกันซึ่งชื่อไม่แนะนำเลย)

ใช้กรณีสำหรับ IEnumerator

ฉันใช้IEnumeratorเฉพาะไลบรารีและกรอบงาน (ระดับที่ต่ำกว่าเล็กน้อย) ซึ่งฉันได้จัดเตรียมอินเตอร์เฟสIEnumerable ตัวอย่างหนึ่งคือไลบรารีการประมวลผลสตรีมข้อมูลซึ่งจัดเตรียมชุดของวัตถุในforeachลูปแม้ว่าข้อมูลเบื้องหลังจะถูกรวบรวมโดยใช้สตรีมไฟล์และซีเรียลไลเซชันต่างๆ

รหัสลูกค้า

foreach(var item in feed.GetItems())
    Console.WriteLine(item);

ห้องสมุด

IEnumerable GetItems() {
    return new FeedIterator(_fileNames)
}

class FeedIterator: IEnumerable {
    IEnumerator GetEnumerator() {
        return new FeedExplicitIterator(_stream);
    }
}

class FeedExplicitIterator: IEnumerator {
    DataItem _current;

    bool MoveNext() {
        _current = ReadMoreFromStream();
        return _current != null;           
    }

    DataItem Current() {
        return _current;   
    }
}

9

การใช้งานIEnumerableเป็นหลักหมายความว่าวัตถุสามารถทำซ้ำได้ นี่ไม่ได้หมายความว่ามันเป็นอาร์เรย์เนื่องจากมีบางรายการที่ไม่สามารถทำดัชนีได้ แต่คุณสามารถระบุได้

IEnumeratorเป็นวัตถุจริงที่ใช้ในการทำซ้ำ มันควบคุมการเคลื่อนย้ายจากวัตถุหนึ่งไปยังอีกวัตถุหนึ่งในรายการ

เวลาส่วนใหญ่IEnumerable& IEnumeratorถูกใช้อย่างโปร่งใสเป็นส่วนหนึ่งของforeachลูป


6

ความแตกต่างระหว่าง IEnumerable และ IEnumerator:

  • IEnumerable ใช้ IEnumerator ภายใน
  • IEnumerable ไม่ทราบว่ารายการ / วัตถุใดที่กำลังดำเนินการอยู่
  • เมื่อใดก็ตามที่เราผ่าน IEnumerator ไปยังฟังก์ชั่นอื่นมันจะรู้ตำแหน่งปัจจุบันของรายการ / วัตถุ
  • เมื่อใดก็ตามที่เราส่งผ่านคอลเลกชัน IEnumerable ไปยังฟังก์ชันอื่นมันจะไม่ทราบตำแหน่งปัจจุบันของรายการ / วัตถุ (ไม่ทราบว่ารายการใดกำลังดำเนินการอยู่)

    IEnumerable มีหนึ่งวิธี GetEnumerator ()

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}

IEnumerator มีคุณสมบัติหนึ่งที่เรียกว่าปัจจุบันและสองวิธีรีเซ็ต () และ MoveNext () (ซึ่งมีประโยชน์สำหรับการรู้ตำแหน่งปัจจุบันของรายการในรายการ)

public interface IEnumerator
{
     object Current { get; }
     bool MoveNext();
     void Reset();
}

5

IEnumerable เป็นกล่องที่มี Ienumerator IEnumerable เป็นอินเตอร์เฟสพื้นฐานสำหรับคอลเลกชันทั้งหมด foreach loop สามารถทำงานได้หากการรวบรวมใช้ IEnumerable ในรหัสด้านล่างมันอธิบายขั้นตอนของการมีตัวแจงนับของเราเอง ก่อนอื่นให้เรากำหนดคลาสที่เราจะทำการสะสม

public class Customer
{
    public String Name { get; set; }
    public String City { get; set; }
    public long Mobile { get; set; }
    public double Amount { get; set; }
}

ตอนนี้เราจะกำหนด Class ซึ่งจะทำหน้าที่เป็นคอลเลกชันสำหรับลูกค้าระดับของเรา ขอให้สังเกตว่าจะมีการใช้อินเทอร์เฟซ IEnumerable เพื่อให้เราต้องใช้วิธีการ GetEnumerator นี่จะคืนค่าตัวแจงนับที่กำหนดเองของเรา

public class CustomerList : IEnumerable
{
    Customer[] customers = new Customer[4];
    public CustomerList()
    {
        customers[0] = new Customer { Name = "Bijay Thapa", City = "LA", Mobile = 9841639665, Amount = 89.45 };
        customers[1] = new Customer { Name = "Jack", City = "NYC", Mobile = 9175869002, Amount = 426.00 };
        customers[2] = new Customer { Name = "Anil min", City = "Kathmandu", Mobile = 9173694005, Amount = 5896.20 };
        customers[3] = new Customer { Name = "Jim sin", City = "Delhi", Mobile = 64214556002, Amount = 596.20 };
    }

    public int Count()
    {
        return customers.Count();
    }
    public Customer this[int index]
    {
        get
        {
            return customers[index];
        }
    }
    public IEnumerator GetEnumerator()
    {
        return customers.GetEnumerator(); // we can do this but we are going to make our own Enumerator
        return new CustomerEnumerator(this);
    }
}

ตอนนี้เรากำลังจะสร้าง Enumerator ที่กำหนดเองของเราเองดังต่อไปนี้ ดังนั้นเราต้องใช้วิธีการ MoveNext

 public class CustomerEnumerator : IEnumerator
    {
        CustomerList coll;
        Customer CurrentCustomer;
        int currentIndex;
        public CustomerEnumerator(CustomerList customerList)
        {
            coll = customerList;
            currentIndex = -1;
        }

        public object Current => CurrentCustomer;

        public bool MoveNext()
        {
            if ((currentIndex++) >= coll.Count() - 1)
                return false;
            else
                CurrentCustomer = coll[currentIndex];
            return true;
        }

        public void Reset()
        {
            // we dont have to implement this method.
        }
    }

ตอนนี้เราสามารถใช้ foreach วนรอบคอลเลกชันของเราเช่นด้านล่าง;

    class EnumeratorExample
    {
        static void Main(String[] args)
        {

            CustomerList custList = new CustomerList();
            foreach (Customer cust in custList)
            {
                Console.WriteLine("Customer Name:"+cust.Name + " City Name:" + cust.City + " Mobile Number:" + cust.Amount);
            }
            Console.Read();

        }
    }

4

ความเข้าใจในรูปแบบของ Iterator จะเป็นประโยชน์สำหรับคุณ ฉันแนะนำให้อ่านเหมือนกัน

รูปแบบการวนซ้ำ

ในระดับสูงสามารถใช้รูปแบบตัววนซ้ำเพื่อกำหนดวิธีมาตรฐานในการวนซ้ำผ่านคอลเลกชันทุกประเภท เรามีผู้เข้าร่วม 3 คนในรูปแบบตัววนซ้ำการรวบรวมจริง (ไคลเอนต์) ตัวรวบรวมและตัววนซ้ำ ผลรวมคือคลาสอินเตอร์เฟส / นามธรรมที่มีเมธอดที่ส่งคืนตัววนซ้ำ Iterator เป็นอินเทอร์เฟซ / นามธรรมคลาสที่มีวิธีการอนุญาตให้เราทำซ้ำผ่านคอลเลกชัน

ในการใช้รูปแบบนั้นเราต้องใช้ตัววนซ้ำเพื่อสร้างคอนกรีตที่สามารถย้ำผ่านคอลเลกชันที่เกี่ยวข้อง (ไคลเอนต์) จากนั้นคอลเลกชัน (ไคลเอนต์) จะใช้ตัวรวบรวมเพื่อส่งคืนอินสแตนซ์ของตัววนซ้ำข้างต้น

นี่คือแผนภาพ UML รูปแบบการวนซ้ำ

ดังนั้นโดยทั่วไปใน c #, IEnumerable คือผลรวมที่เป็นนามธรรมและ IEnumerator เป็น Iterator ที่เป็นนามธรรม IEnumerable มีวิธีการเดียวคือ GetEnumerator ที่รับผิดชอบในการสร้างอินสแตนซ์ของ IEnumerator ประเภทที่ต้องการ คอลเลกชันเช่นรายการใช้ IEnumerable

ตัวอย่าง. สมมติว่าเรามีวิธีgetPermutations(inputString)ที่ส่งกลับพีชคณิตทั้งหมดของสตริงและเมธอดส่งคืนอินสแตนซ์ของIEnumerable<string>

เพื่อนับจำนวนการเปลี่ยนลำดับเราสามารถทำสิ่งต่าง ๆ ดังนี้

 int count = 0;
        var permutations = perm.getPermutations(inputString);
        foreach (string permutation in permutations)
        {
            count++;
        }

คอมไพเลอร์ c # มากหรือน้อยแปลงข้างต้นเป็น

using (var permutationIterator = perm.getPermutations(input).GetEnumerator())
        {
            while (permutationIterator.MoveNext())
            {
                count++;
            }
        }

หากคุณมีคำถามใด ๆ โปรดอย่าลังเลที่จะถาม


2

ผลงานเล็กน้อย

หลายคนอธิบายเกี่ยวกับ 'เมื่อใช้' และ 'ใช้กับ foreach' ฉันคิดว่าการเพิ่ม Another States Differenceที่นี่ตามที่ร้องขอในคำถามเกี่ยวกับความแตกต่างระหว่าง IEnumerable และ IEnumerator

ฉันสร้างตัวอย่างโค้ดด้านล่างตามหัวข้อการสนทนาด้านล่าง

IEnumerable, IEnumerator vs foreach เมื่อใดควรใช้ IEnumerator และ IEnumerable ต่างกันอย่างไร

ตัวแจงนับรักษาสถานะ (ตำแหน่งการวนซ้ำ) ระหว่างการเรียกใช้ฟังก์ชันในขณะที่การทำซ้ำในมืออื่น ๆ ที่นับไม่ได้

นี่คือตัวอย่างการทดสอบที่มีความคิดเห็นที่จะเข้าใจ

ผู้เชี่ยวชาญโปรดเพิ่ม / แก้ไขฉัน

static void EnumerableVsEnumeratorStateTest()
{
    IList<int> numList = new List<int>();

    numList.Add(1);
    numList.Add(2);
    numList.Add(3);
    numList.Add(4);
    numList.Add(5);
    numList.Add(6);

    Console.WriteLine("Using Enumerator - Remembers the state");
    IterateFrom1to3(numList.GetEnumerator());

    Console.WriteLine("Using Enumerable - Does not Remembers the state");
    IterateFrom1to3Eb(numList);

    Console.WriteLine("Using Enumerable - 2nd functions start from the item 1 in the collection");
}

static void IterateFrom1to3(IEnumerator<int> numColl)
{
    while (numColl.MoveNext())
    {
        Console.WriteLine(numColl.Current.ToString());

        if (numColl.Current > 3)
        {
            // This method called 3 times for 3 items (4,5,6) in the collection. 
            // It remembers the state and displays the continued values.
            IterateFrom3to6(numColl);
        }
    }
}

static void IterateFrom3to6(IEnumerator<int> numColl)
{
    while (numColl.MoveNext())
    {
        Console.WriteLine(numColl.Current.ToString());
    }
}

static void IterateFrom1to3Eb(IEnumerable<int> numColl)
{
    foreach (int num in numColl)
    {
        Console.WriteLine(num.ToString());

        if (num>= 5)
        {
            // The below method invokes for the last 2 items.
            //Since it doesnot persists the state it will displays entire collection 2 times.
            IterateFrom3to6Eb(numColl);
        }
    }
}

static void IterateFrom3to6Eb(IEnumerable<int> numColl)
{
    Console.WriteLine();
    foreach (int num in numColl)
    {
        Console.WriteLine(num.ToString());
    }
}

2

ฉันสังเกตเห็นความแตกต่างเหล่านี้:

A. เราย้ำรายการในวิธีที่แตกต่าง foreach สามารถใช้สำหรับ IEnumerable และในขณะที่วนรอบสำหรับ IEnumerator

B. IEnumerator สามารถจดจำดัชนีปัจจุบันได้เมื่อเราส่งจากวิธีหนึ่งไปยังอีกวิธีหนึ่ง (มันเริ่มทำงานกับดัชนีปัจจุบัน) แต่ IEnumerable ไม่สามารถจำดัชนีและรีเซ็ตดัชนีเป็นจุดเริ่มต้นได้ เพิ่มเติมในวิดีโอนี้https://www.youtube.com/watch?v=jd3yUjGc9M0


1

IEnumerableและIEnumeratorทั้งสองเป็นส่วนต่อประสานใน C #

IEnumerableเป็นอินเตอร์เฟสที่กำหนดวิธีการเดียวGetEnumerator()ที่ส่งคืนIEnumeratorอินเตอร์เฟส

ใช้งานได้กับการเข้าถึงคอลเลกชันแบบอ่านอย่างเดียวซึ่งIEnumerableใช้กับforeachคำสั่งได้

IEnumeratorมีสองวิธีMoveNextและReset. นอกจากนี้ยังมีคุณสมบัติที่เรียกว่าCurrentนอกจากนี้ยังมีคุณสมบัติที่เรียกว่า

ข้อมูลต่อไปนี้แสดงการใช้งาน IEnumerable และ IEnumerator


0
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Enudemo
{

    class Person
    {
        string name = "";
        int roll;

        public Person(string name, int roll)
        {
            this.name = name;
            this.roll = roll;
        }

        public override string ToString()
        {
            return string.Format("Name : " + name + "\t Roll : " + roll);
        }

    }


    class Demo : IEnumerable
    {
        ArrayList list1 = new ArrayList();

        public Demo()
        {
            list1.Add(new Person("Shahriar", 332));
            list1.Add(new Person("Sujon", 333));
            list1.Add(new Person("Sumona", 334));
            list1.Add(new Person("Shakil", 335));
            list1.Add(new Person("Shruti", 336));
        }

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



    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();  // Notice here. it is simple object but for 
                                //IEnumerator you can get the collection data

            foreach (Person X in d)
            {
                Console.WriteLine(X);
            }

            Console.ReadKey();
        }
    }
}
/*
Output : 

Name : Shahriar  Roll : 332
Name : Sujon     Roll : 333
Name : Sumona    Roll : 334
Name : Shakil    Roll : 335
Name : Shruti    Roll : 336
  */
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.