ตอน 28 IEnumerator, Indexer และ Iterator

ไปหน้าแรก | สารบัญ | Laploy.comระเบียนบทความ | บทความจากลาภลอย

เว็บไซต์นี้เป็นตัวอย่างเนื้อหาบางตอนในหนังสือ "เรียนรู้ด้วยตนเอง OOP C# ASP.NET" ครอบคลุม บทที่ 1 ถึงบทที่ 6 (ในหนังสือมี 21 บท) เนื้อหาใน Blog อาจอาจแตกต่างจากในหนังสือเพราะเป็นเนื้อหาที่ยังไม่ได้ตรวจแก้ขัดเกลา (edit)

กดที่นี่เพื่อดูรายละเอียดเนื้อหาในแต่ละบท

กดที่นี่เพื่อไปยังเว็บบอร์ด ถาม-ตอบ 

 

IEnumerator

IEnumerator ช่วยให้เราเข้าถึงชุดข้อมูลด้วยการวนซ้ำได้ ปรกติหากเราต้องการทำงานวนซ้ำเพื่อดึงข้อมูลใน array เราสามารถใช้คำสั่ง Foreach ดังนี้

static void Main(string[] args)
{
string[] engClass = { "Kanjana", "Wattana", "Bunchai" };
foreach (string student in engClass)
{
Console.WriteLine(student);
}
}

ตัวอย่างโค้ดข้างบนแสดงการวนซ้ำเพื่อดึงข้อมูลทั้งหมดใน array ชื่อ engClass (เก็บรายชื่อนักเรียนในวิชาภาษาอังกฤษ) โดยการใช้คำสั่ง Foreach สาเหตุที่เราใช้ Foreach กับ array ได้เพราะคลาส array ได้จัดให้มีอินเตอร์เฟส IEnumerator ไว้ภายใน หากเราสร้างชุดข้อมูล (custom data collection) ของเราเองขึ้นและต้องการให้ทำงานกับ Foreach ได้ก็สามารถทำได้เช่นกัน โดยจัดให้มีอินเตอร์เฟส IEnumerator ไว้ภายในคลาสของเรา ยกตัวอย่างเช่น แทนที่จะใช้ array เหมือนในตัวอย่างบน เรากลับสร้างคลาสเพื่อทำหน้าที่เป็น object ข้อมูลนักเรียนแทน โดยขั้นแรกเราต้องสร้างคลาส Student ก่อนดังนี้

public class Student
{
private string firstname;
public string Firstname
{
get { return firstname; }
set { firstname = value; }
}
public Student(string s)
{
firstname = s;
}
}

ขั้นต่อไปสร้างคลาสที่จะทำหน้าที่เป็น collection ของคลาส Student ดังนี้

    public class ClassList : IEnumerable
{
private ArrayList students;
public ClassList()
{
students = new ArrayList();
students.Add(new Student("somsree"));
students.Add(new Student("Kanjana"));
students.Add(new Student("Bunchar"));
}
public IEnumerator GetEnumerator();
}
private class AllStudent :
IEnumerator
{
}

โปรดสังเกตว่าในคลาส ClassList นี้ inherit จาก interface ชื่อ IEnumerable ซึ่งเป็นคลาสที่นิยามไว้ใน .NET Framework System.Collections เนื่องจากคลาส IEnumerable นี้มีนิยาม properties ชื่อ GetEnumerator() ดังนั้นคลาส ClassList จึงจำเป็นต้องใส่นิยามและโค้ดทำงานดังนี้

public IEnumerator GetEnumerator()
{
return (IEnumerator)new AllStudent(this);
}

โปรดสังเกตว่าสิ่งที่เราส่งกลับไปเป็นค่าของ properties นี้คือค่าที่ได้จากคลาส AllStudent() ซึ่งเป็นคลาสย่อยภายใน (nested class) ที่เราเขียนขึ้นเพื่อจัดการกับการวนรอบใน collection คลาส AllStudent() มีโค้ดดังนี้

    private class AllStudent : IEnumerator
{
private ClassList classList;
private int studentIndex;

public AllStudent(ClassList c)
{
classList = c;
studentIndex = -1;
}
public void Reset()
{
studentIndex = -1;
}
public object Current
{
get { return classList.students[studentIndex]; }
}
public bool MoveNext()
{
studentIndex++;
if (studentIndex >= classList.students.Count)
return false;
else
return true;
}
}

คลาส Student คือคลาสที่เราจะนำไปสร้าง object ที่ต้องการทำให้ใช้กับ Foreach ได้เหมือนarray ส่วน ClassList คือคลาสซึ่งทำหน้าที่เป็น object collection และคลาส AllStudent ทำหน้าที่ให้เกิดการวนรอบได้ โดยเราทำเป็น nested class ที่ inherit จาก interface IEnumerable

 

โปรดสังเกตว่าคลาสนี้ inherit จากคลาส IEnumerator ซึ่งเป็น interface ที่มีข้อกำหนดให้ derived class ต้องนิยามและใส่ส่วนโค้ด method สาม method คือ Reset(), Current() และ MoveNext() สุดท้ายคือโค้ดตัวอย่างการเรียกใช้งาน

public class MyClass
{
public static void Main()
{
ClassList myClass = new ClassList();
foreach (Student student in myClass)
Console.WriteLine(student.Firstname);
Console.ReadLine();
}
}

ผลลัพธ์ที่ได้จากการรันโปรแกรมนี้คือ

somsree
Kanjana
Bunchar

ข้อสรุปเรื่อง IEnumerator
• ใช้เพื่อให้ทำ Foreach กับ object collection ได้
• Array และ Queue ใช้ IEnumerator
• ทำงานได้กับหลาย client พร้อมๆ กัน
• เรียกใช้งานได้ผ่าน Foreach
• เรียกใช้โดยตรง (โดยไม่ผ่าน Foreach) ก็ได้

 

Indexer

Indexer คล้าย Properties แตกต่างกันที่เราใช้ properties เพื่ออ่านหรือเขียน data field ของ object แต่เราใช้ indexer เพื่ออ่านหรือเขียนค่าของตัว object เอง โดยเป็น object ที่มีชุดของ object ย่อยอยู่ภายใน (object collection)

ยกตัวอย่างเช่นสินค้าหมวดหนึ่งๆ จะมีสินค้ามากกว่าหนึ่งอย่าง เมื่อเรานิยามคลาส Catalog ทำหน้าที่แสดงหมวดสินค้า และคลาส Product ทำหน้าที่แสดงตัวสินค้า เราต้องหาทางทำให้คลาส Catalog มี object ซึ่งทำหน้าที่แสดง Product หลายๆ instance อยู่ภายใน จากนั้นเราก็ให้ class client เข้าถึง object ซึ่งทำหน้าที่แสดงสินค้า (คือ product) ในลักษณะเดียวกันทั้งหมด ซึ่งทำได้โดยใช้ indexer

Indexer ช่วยให้เราจัดการกับ instance ของคลาสหรือ struc ได้แบบเดียวกับ array อีกจุดหนึ่งที่ indexer แตกต่างจาก properties คือตัวเรียกใช้ (accessors คือ set และ get) ของ indexer จะมีพารามิเตอร์ด้วย ขณะที่ properties ไม่มี

ต่อไปนี้เป็นตัวอย่างโปรแกรมสาทิตการสร้างและใช้งาน indexer โดยเราจะสร้างคลาสสองคลาส คลาสแรกชื่อ ProductCatalog ทำหน้าที่เก็บข้อมูลสินค้า และมี indexer อยู่ภายในเพื่อให้ class client สามารถเรียกข้อมูลสินค้าโดยอ้างถึง object ได้เหมือน array อีกคลาสคือ Program เป็นคลาสแสดงวิธีเรียกใช้คลาสที่มี indxer

class ProductCatalog
{
string[] Product = { "Food", "Cloth", "Car" };
private int GetProductNumber(string testName)
{
int i = 100;
foreach (string productName in Product)
{
if (productName == testName)
{
return i;
}
i++;
}
return -1;
}
public int this[string productName]
{
get
{
return (GetProductNumber(productName));
}
}
}

โค้ดที่เห็นข้างบนคือนิยามคลาส ProductCatalog ซึ่งมี array ชื่อ Product ทำหน้าที่เป็นตัวอย่าง object เก็บข้อมูลสินค้าภายในคลาสนี้ (ในการใช้งานจริงเราอาจสร้าง object จาก struct หรือ class) มี method ชื่อ GetProductNumber ทำหน้าที่แสดงหมายเลขของสินค้าแต่ละชิ้น และมี indexer ซึ่งจะเรียกใช้งาน method นี้

class Program
{
static void Main(string[] args)
{
ProductCatalog myPD = new ProductCatalog();
Console.WriteLine(myPD["Food"]);
Console.WriteLine(myPD["Cloth"]);
Console.WriteLine(myPD["Car"]);
Console.WriteLine(myPD["House"]);
Console.Read();
}
}

Indexer ช่วยให้เราเข้าถึงกลุ่ม object ที่อยู่ภายใน object collection โดยการอ้างดัชนีได้เหมือน array ภายในคลาสจะมี indexer ได้เพียงหนึ่งตัวซึ่งจะมีชื่อเดียวกับคลาส แต่เราอาจทำ overload ต่อ indexer ได้ ในภาพนี้ object คือสิ่งที่ถูกสร้างจากคลาส Product ส่วนคลาสที่นิยาม object collection คือ ProductCatalog

 

โค้ดที่เห็นข้างบนคือคลาส Program ทำหน้าที่แสดงตัวอย่างการใช้งาน indexer ที่อยู่ภายในคลาส ProductCatalog รายละเอียดการทำงานของโปรแกรมมีขั้นตอนดังนี้

  1. method Main สร้าง object จากคลาส ProductCatalog โดยใช้ชื่อตัวแปรอ้างอิงเป็น myPD นี่คือ object ที่เราต้องการใช้งานมันด้วยการไล่ไปตามดัชนีได้อย่าง array
  2. เมื่อสร้าง object แล้วเราสามารถใส่วงเล็บเหลี่ยมหลังชื่อตัวแปร myPD ได้เหมือน array ภายในวงเล็บเราใส่ชื่อสินค้าที่เราอยากรู้หมายเลขของมัน
  3. C# compiler จะรู้ว่าคำสั่ง myPD[“Food”] คือการเรียกใช้ indexer ที่อยู่ภายในคลาส ProductCatalog มันจึงไปทำงานที่คลาส ProductCatalog ใน indexer มีคำสั่ง get ซึ่งมีคำสั่ง return คืนค่าเป็น int (เพราะที่ส่วนประกาศของ indexer เรากำหนด type เป็น int ไว้) ภายในวงเล็บหลังคำสั่ง return เราเรียก method GetProductNumber โดยส่งค่า ProductName (ขณะนี้เป็น “Food”) ให้เป็นพารามิเตอร์

  4. จากนั้นโปรแกรมจะไปทำงานที่ method GetProductNumber ซึ่งมีคำสั่ง foreach ทำการวนรอบเพื่อสร้างหมายเลขสินค้า โดยเราต้องการเริ่มจากหมายเลข 100 ดังนั้น “Food” ซึ่งเป็นข้อมูลใน array element แรก จึงได้ผลลัพธ์เป็น 100

ผลลัพธ์ของการทำงานของโค้ดตัวอย่างคือ

100
101
102
-1

ข้อสรุปเกี่ยวกับ indexer

• indexer ช่วยให้เราเข้าถึงชุดของ object ภายใน collection ได้โดยใช้ดัชนี
• indexer จะมีชื่อเดียวกับคลาสที่มันเป็นสมาชิก
• ภายในหนึ่งคลาสมี indexer ได้เพียงตัวเดียว
• ทำ overload ต่อ indexer ได้
• indexer ใช้ this เพื่อนำเสนอ instance นั้นๆ ของ object
• indexer มี accessors (คือ set และ get) เช่นเดียวกับ properties
• accessors ของ indexer ต้องมีพารามิเตอร์
• indexer ใช้ index operator (คือวงเล็บเหลี่ยม) เช่นเดียวกับ array
• indexer เป็น static ไม่ได้
• indexer มีได้ภายในคลาส struct และ interface

 

Iterator, yield

เนื่องจากการใช้ IEnumerator ยุ่งยากเกินไป ภาษา C# 2.0 จึงจัดให้มี Iterator ขึ้นทดแทน Iterator คือ method ที่มี get/set เหมือ properties ช่วยให้เราใช้ foreach เพื่อเข้าถึงค่าในคลาสหรือ struct ได้โดยไม่ต้องใช้ IEnumerator

แทนที่จะใช้ IEnumerator เราก็เพียงสร้าง Iterator ซึ่งทำหน้าที่ท่องไปในโครงสร้างข้อมูลของคลาส เมื่อคอมไพเลอร์ตรวจพบ Iterator ในโปรแกรมของเรา มันจะสร้างเมธอด Current, MoveNext และ Dispose ของ IEnumerator ให้โดยอัตโนมัติ ต่อไปนี้เป็นตัวอย่างโค้ดสาทิตการสร้างและใช้งาน Iterator

public class EngClass : IEnumerable
{
string[] student = { "Somsak", "Boonme", "Vichai" };
public IEnumerator GetEnumerator()
{
for (int i = 0; i < student.Length; i++)
{
yield return student[i];
}
}
}

โค้ดข้างบนคือตัวอย่าง collection class ง่ายๆ ชื่อ EngClass ใช้เก็บชื่อนักเรียนในชั้นเรียนวิชาภาษาอังกฤษ โปรดสังเกตว่าคลาส EngClass inherit จาก interface ชื่อ IEnumerable ซึ่งเป็น interface ที่ถูกนิยามไว้ใน System.Collections

 

เราควรใช้ Iterator และ yield แทนการใช้ IEnumerator แบบเดิมที่ยุ่งยากกว่า เพราะการใช้งาน IEnumerator แบบเดิมเราต้องสร้างคลาสจัดการกับการวนรอบเอง ซึ่งเป็นคลาสที่ inherit จาก interface IEnumerator และมีนิยาม method Reset(), Current() และ MoveNext()

ส่วนการใช้งาน Iterator เราไม่ต้องสร้างคลาสจัดการ เราเพียงใช้คำสั่ง yield ร่วมกับ type IEnumerator compiler จะสร้างคลาสจัดการกับการวนรอบให้เรา ซึ่งเป็นคลาสที่ inherit จาก interface IEnumerator และมีนิยาม method Reset(), Current() และ MoveNext() โดยอัตโนมัติ (โค้ดที่สร้างให้จะปรากฏใน MSIL)

 

ในคลาส EngClass มี method ชื่อ GetEnumerator() ทำหน้าที่เป็น Iterator โดย method นี้มี return type เป็น IEnumerator และการส่งค่ากลับจะใส่ keyword yield ไว้หน้า keyword return ซึ่ง keyword yield ทำหน้าที่จัดให้มีค่าของ object ที่ถูกวนอ่านค่า (ในที่นี้คือ array student[]) ต่อไปเป็น โค้ดทดสอบการใช้งานตัว Iterator

class test
{
static void Main()
{
EngClass studentName = new EngClass();
foreach (string name in studentName)
{
Console.Write(name + " ");
}
}
}

โค้ดข้างบนสร้าง object collection ชื่อ studentName จาก Iterator EngClass จากนั้นเราจึงสามารถใช้คำสั่ง foreach เพื่อวนอ่านค่าทั้งหมดใน collection ได้

ผลลัพธ์จากการทำงานของโปรแกรมนี้คือ

Somsak Boonme Vichai

ข้อสรุปเรื่อง Iterator
• ใช้งานได้สะดวกกว่าการทำ IEnumerator แบบเดิม
• Iterator ต้องใช้ร่วมกับคำสั่ง yield
• การรับส่งค่าใช้ get/set ก็ได้ หรือจะส่งกลับที่ตัว method เองก็ได้ (ในตัวอย่างเป็นแบบหลัง)
• ใช้ร่วมกับ foreach เพื่อวนรอบอ่านค่า object ใน collection
• ในหนึ่งคลาสมีหลาย Iterator ได้
• return type ต้องเป็น IEnumerable หรือ IEnumerator

ตอนต่อไป: Regex (Regular Expression)

Post a comment or leave a trackback: Trackback URL.

ความเห็น

  • sathit  On มกราคม 26, 2007 at 6:29 pm

    ขอบคุณครับ  ผมติดตามตลอด

  • Mac  On กุมภาพันธ์ 27, 2007 at 2:29 pm

    แจ๋วครับ

  • suchart  On เมษายน 30, 2008 at 10:59 am

    เยี่ยมเลยครับ อาจารย์ ชอบมากเลยครับเรื่อง yield
    Code ผมสั้นลงเป็นกองเลยครับ

ใส่ความเห็น

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / เปลี่ยนแปลง )

Twitter picture

You are commenting using your Twitter account. Log Out / เปลี่ยนแปลง )

Facebook photo

You are commenting using your Facebook account. Log Out / เปลี่ยนแปลง )

Google+ photo

You are commenting using your Google+ account. Log Out / เปลี่ยนแปลง )

Connecting to %s

%d bloggers like this: