วันพุธที่ 29 ตุลาคม พ.ศ. 2551

เพิ่มเติมกับ IEnumerator, IEnumerable และ ICloneable

จากที่ผ่านมาได้พูดถึงภาพรวมของ Collection Interface ที่ .Net ได้เตรียมไว้ คราวนี้จะมาดูรายละเอียด ของ Interface 3 ตัวคือ IEnumerator, IEnumerable และ ICloneable (ที่จริงจะกล่าวเฉพาะ IEnumerator และ IEnumerable แต่ในตัวอย่างมีการใช้งาน ICloneable ด้วยก็เลยกล่าวเพิ่มไปซะเลย)

IEnumerator เป็น interface พื้นฐานเพื่อให้ class ที่อ้างถึง interface นี้สามารถใช้ foreach ได้ ภายในมีสมาชิกดังนี้
    Methods
  • MoveNext ระบุให้เลื่อน element ปัจจุบันไป 1 ช่อง
  • Reset สังให้ current กลับไปจุดเริ่มต้น

    Properties
  • Current ดึง element ปัจจุบันจาก collection

IEnumerable เป็น interface ที่จะทำการส่งคลาส IEnumerator ไปยังคำสั่ง foreach ภายในมีสมาชิกดังนี้
    Methods
  • GetEnumerator ส่งค่า enumerator ที่จะส่งไปยัง foreach

ICloneable เป็น interface ที่กำหนดให้คลาสที่ประกาศต้องมีความสามารถในการ clone ตัวมันเอง

    Methods
  • Clone ทำการ clone ตัวมันเองแล้วส่งค่านั้นกลับไป

ตัวอย่าง Code:
ตัวอย่างต่อไปนี้จะเป็นการสร้าง single linklist โดยไม่ใช้คลาสในกลุ่ม collection ของ .Net แต่จะกำหนดโครงสร้างทั้งหมดเองตามลักษณะดังนี้

|--------------- MyList -----------------|
|---Node---|   |---Node---|   |---Node---|
|Value|Next|-->|Value|Next|-->|Value|Next|

จากโครงสร้างดังกล่าวจะมีคลาสสองตัวคือ MyList และ Node โดย MyList จะเป็น collection ของ Node ภายใน Node จะมีสมาชิกภายในคือ Value เก็บค่าตัวเลขที่ต้องการบันทึก และ Next เก็บที่อยู่ของ Node ถัดไป


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

namespace LinkListDemo
{
    // ประกาศคลาส Node
    public class Node : ICloneable //ประกาศใช้ ICloneable
        public int Value;
        public Node Next;
 
        public Node()
        {
            _value = 0;
        }
 
        public Node(int nValue)
        {
            _value = nValue;
        }
 
        public override string ToString()
        {
            return _value.ToString();
        }
 
        #region ICloneable Members
        public object Clone()
        {
            return new Node(_value);         }
        #endregion
    }
 
    // ประกาศคลาส MyList
    public class MyList : IEnumerator, IEnumerable, ICloneable
    {
        private Node _firstNode;
        private Node _curNode;         private Node _lastNode;
 
        public MyList()
        {
            _firstNode = new Node(); //ตำแหน่ง 1.1
        }
 
        public bool MoveNext()
        {
            _curNode = _curNode.Next; //ตำแหน่ง 1.2
            return !(_curNode == null);
        }
 
        public void Reset()
        {
            _curNode = _firstNode; //ตำแหน่ง 1.3
        }
 
        public object Current //ตำแหน่ง 1.4
        {
            get
            {
                if (_curNode != null)
                {
                    return _curNode;
                }
                else                 {
                    throw new InvalidOperationException();
                }
            }
        }
 
        public void Add(Node newNode)
        {
            if (_curNode == null)
            {
                _firstNode.Next = newNode;
                _curNode = newNode;
                _lastNode = newNode;
            }
            else
            {
                _lastNode.Next = newNode;
                _lastNode = newNode;
            }
        }
 
        public void Add(int newValue)
        {
            Node newNode = new Node(newValue);
            this.Add(newNode);
        }
 
        #region IEnumerable Members
        public IEnumerator GetEnumerator() //ตำแหน่ง 2
        {
            return (IEnumerator)Clone();
        }
        #endregion
 
        #region ICloneable Members
        public object Clone()
        {
            Node tmpNode = _curNode;
            MyList newList = new MyList();
            _curNode = _firstNode.Next;
            while (_curNode != null)
            {
                newList.Add(_curNode.Value);
                _curNode = _curNode.Next;
            }
            return newList;
        }
        #endregion
    }
 
    class Program //โปรแกรมเรียกใช้ class MyList
    {
        static void Main(string[] args)
        {
            MyList myList = new MyList();
            for (int cnt = 0; cnt < 10; cnt++)
            {
                myList.Add(new Node(cnt));
            }
            foreach (Node node in myList)
            {
                Console.WriteLine(node);
            }
            Console.ReadKey();
        }
    }

ในการวนรอบของ foreach นั้นคำสั่ง foreach จะเรียก GetEnumerable() (ตำแหน่ง 2) เพื่อเรียก object collector ขึ้นมาโดยที่ object collector ที่คืนกลับนั้นคลาสจะต้องประกาศ IEnumerator ไว้ด้วย หลังจาก foreach ได้ object collector จะมีขั้นตอนการดำเนินการดังนี้
  1. เรียก MoveNext(ตำแหน่ง 1.2) เพื่อเลื่อนตำแหน่งของ element ไปยังจุดถัดไป ถ้าผลของการ MoveNext ส่งกลับมาเป็น true จะทำงานในข้อ 2 ต่อ ถ้าเป็น false จะหยุดการทำงาน
  2. ส่งค่าใน element ออกไปผ่าน property Current (ตำแหน่ง 1.4)
  3. วนกลับไปทำข้อ 1 ใหม่

จากขั้นตอนดังกล่าวจะเห็นว่าเมื่อเริ่มต้นนั้นการวนรอบของ foreach นั้นตำแหน่งของ Current นั้นจะต้องชี้ไปยังตำแหน่งก่อนหน้าค่าแรก (ในกรณีที่เป็น array ที่มีตำแหน่งเริ่มต้นที่ array[0] ตำแหน่งของ current จะต้องชี้ที่ตำแหน่ง -1) เนื่องจาก foreach จะเรียกใช้คำสั่ง MoveNext (ตำแหน่ง 1.3) ก่อนแล้วจึงส่ง element ใน collector ออกไป ดังนั้นในตอนที่สร้าง MyList ขึ้นมาจึงได้สร้าง node ใหม่ให้กับ _firstNode ในทันทีเพื่อป้องกันความผิดพลาดที่จะเกิดจากการอ่านข้อมูลจาก object ที่ไม่มีการอ้างถึง

ไม่มีความคิดเห็น: