IEnumerable 结合 IEnumerator 的设计思想
(任何引用请注明:转载于美人山下http://beautyhill.blogspot.com)
最近一直在考虑“面向接口编程”的思想。接口的作用大体是为了更好的规范、扩展能力,尤其重要的,它使得一个大的部件在功能上进行有机划分;这种分割使得功能职责简洁明了,确保开发人员不易出错。然则,我一直不甚明白“*able”与“*ator”的关系;或者说,为什么不用一个单单的“*ator”,设计成配对的目的是什么?
以下摘自http://codebetter.com/blogs/david.hayden/archive/2005/03/08/59419.aspx
--------------------------------------------------------------------------------
Implementing IEnumerable and IEnumerator on Your Custom Objects
Often you want to be able to enumerate through a collection of objects using the foreach statement in C#:
Using foreach in C#
foreach (Student student in myClass)
Console.WriteLine(student);
To be able to pull that off, myClass must implement IEnumerable:
IEnumerable Defined
Exposes the enumerator, which supports a simple iteration over a collection.
public IEnumerator GetEnumerator();
As is says above, IEnumerable just exposes another interface, called IEnumerator:
IEnumerator Defined
Supports a simple iteration over a collection. Enumerators only allow reading the data in the collection. Enumerators cannot be used to modify the underlying collection.
public object Current;
public void Reset();
public bool MoveNext();
To see this in action, let's create an example by which we can iterate through a list of students in a class using the foreach statement in C#.
First, we need to create a Student Class as shown below. At the minimum we want to override ToString() (Overriding System.Object.ToString() and IFormattable) so it says something meaningful and for kicks I decided to override Equals() and GetHashCode() (Object Identity vs. Object Equality - Overriding System.Object.Equals(Object obj)) only as an example.
Student Class
public class Student
{
#region Private Members
private readonly string _id;
private string _firstname;
private string _lastname;
#endregion
#region Properties
public string ID { get { return _id; } }
public string Firstname
{
get { return _firstname; }
set { _firstname = value; }
}
public string Lastname
{
get { return _lastname; }
set { _lastname = value; }
}
#endregion
#region Constructors
public Student(string id, string firstname, string lastname)
{
_id = id;
_firstname = firstname;
_lastname = lastname;
}
#endregion
#region Overriding System.Object
public override string ToString()
{
return String.Format("{0} {1}, ID: {2}", _firstname, _lastname, _id);
}
public override bool Equals(object obj)
{
if (obj == null) return false;
if (Object.ReferenceEquals(this,obj)) return true;
if (this.GetType() != obj.GetType()) return false;
Student objStudent = (Student)obj;
if (_id.Equals(objStudent._id)) return true;
return false;
}
public override int GetHashCode()
{
return _id.GetHashCode();
}
#endregion
}
We now need a custom class, called ClassList, that essentially holds the students. Forgetting IEnumerable for a moment, here is my barebones class. The class contains an ArrayList, called _students, which is populated with 3 students in its constructor. The list of students is private to ClassList and is currently not exposed outside of the class.
ClassList Class with no IEnumerable
public class ClassList
{
#region Private Members
private readonly string _id;
private ArrayList _students;
#endregion
#region Properties
public string ID { get { return _id; } }
#endregion
#region Constructors
public ClassList(string id)
{
_id = id;
_students = new ArrayList();
_students.Add(new Student("12345", "John", "Smith"));
_students.Add(new Student("09876", "Jane", "Doe"));
_students.Add(new Student("76403", "Bob", "Johnson"));
}
#endregion
}
To support iteration using foreach on the class, I need to implement IEnumerable on ClassList. Since my students are in an ArrayList, and I know ArrayList implements IEnumerable, I am going to "cheat" and pass back the IEnumerator for _students:
Using The ArrayList's IEnumerator
public IEnumerator GetEnumerator()
{
return (_students as IEnumerable).GetEnumerator();
}
This will allow me to use foreach on ClassList and here is sample code you can run to see how it works:
Example 1: Testing The ArrayList's IEnumerator
using System;
using System.Collections;
public class Student
{
#region Private Members
private readonly string _id;
private string _firstname;
private string _lastname;
#endregion
#region Properties
public string ID { get { return _id; } }
public string Firstname
{
get { return _firstname; }
set { _firstname = value; }
}
public string Lastname
{
get { return _lastname; }
set { _lastname = value; }
}
#endregion
#region Constructors
public Student(string id, string firstname, string lastname)
{
_id = id;
_firstname = firstname;
_lastname = lastname;
}
#endregion
#region Overriding System.Object
public override string ToString()
{
return String.Format("{0} {1}, ID: {2}", _firstname, _lastname, _id);
}
public override bool Equals(object obj)
{
if (obj == null) return false;
if (Object.ReferenceEquals(this,obj)) return true;
if (this.GetType() != obj.GetType()) return false;
Student objStudent = (Student)obj;
if (_id.Equals(objStudent._id)) return true;
return false;
}
public override int GetHashCode()
{
return _id.GetHashCode();
}
#endregion
}
public class ClassList : IEnumerable
{
#region Private Members
private readonly string _id;
private ArrayList _students;
#endregion
#region Properties
public string ID { get { return _id; } }
#endregion
#region Constructors
public ClassList(string id)
{
_id = id;
_students = new ArrayList();
_students.Add(new Student("12345", "John", "Smith"));
_students.Add(new Student("09876", "Jane", "Doe"));
_students.Add(new Student("76403", "Bob", "Johnson"));
}
#endregion
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
return (_students as IEnumerable).GetEnumerator();
}
#endregion
}
public class MyClass
{
public static void Main()
{
ClassList myClass = new ClassList("History 204");
foreach (Student student in myClass)
Console.WriteLine(student);
Console.ReadLine();
}
}
If that is all the functionality you need, then passing the ArrayList's IEnumerator is the ticket. No sense creating your own IEnumerator if you don't have to.
However, we can create our own class, called ClassEnumerator, that implements IEnumerator and accomplishes the same thing as above. The class essentially just iterates through the _students arraylist using an index. Reset() sets the index back to -1. MoveNext() jumps ahead and returns a boolean as to whether we have hit the end. And, the Current property gets the current student.
ClassEnumerator
private class ClassEnumerator : IEnumerator
{
private ClassList _classList;
private int _index;
public ClassEnumerator(ClassList classList)
{
_classList = classList;
_index = -1;
}
#region IEnumerator Members
public void Reset()
{
_index = -1;
}
public object Current
{
get
{
return _classList._students[_index];
}
}
public bool MoveNext()
{
_index++;
if (_index >= _classList._students.Count)
return false;
else
return true;
}
#endregion
}
We will stick ClassEnumerator within ClassList and pass it back via the IEnumerable interface. Here is the entire code listing using our custom IEnumerator:
Testing ClassEnumerator
using System;
using System.Collections;
public class Student
{
#region Private Members
private readonly string _id;
private string _firstname;
private string _lastname;
#endregion
#region Properties
public string ID { get { return _id; } }
public string Firstname
{
get { return _firstname; }
set { _firstname = value; }
}
public string Lastname
{
get { return _lastname; }
set { _lastname = value; }
}
#endregion
#region Constructors
public Student(string id, string firstname, string lastname)
{
_id = id;
_firstname = firstname;
_lastname = lastname;
}
#endregion
#region Overriding System.Object
public override string ToString()
{
return String.Format("{0} {1}, ID: {2}", _firstname, _lastname, _id);
}
public override bool Equals(object obj)
{
if (obj == null) return false;
if (Object.ReferenceEquals(this,obj)) return true;
if (this.GetType() != obj.GetType()) return false;
Student objStudent = (Student)obj;
if (_id.Equals(objStudent._id)) return true;
return false;
}
public override int GetHashCode()
{
return _id.GetHashCode();
}
#endregion
}
public class ClassList : IEnumerable
{
#region Private Members
private readonly string _id;
private ArrayList _students;
#endregion
#region Properties
public string ID { get { return _id; } }
public int TotalStudents { get { return _students.Count; } }
#endregion
#region Constructors
public ClassList(string id)
{
_id = id;
_students = new ArrayList();
_students.Add(new Student("12345", "John", "Smith"));
_students.Add(new Student("09876", "Jane", "Doe"));
_students.Add(new Student("76403", "Bob", "Johnson"));
}
#endregion
#region IEnumerable Members
public IEnumerator GetEnumerator()
{
return (IEnumerator)new ClassEnumerator(this);
}
#endregion
#region ClassEnumerator
private class ClassEnumerator : IEnumerator
{
private ClassList _classList;
private int _index;
public ClassEnumerator(ClassList classList)
{
_classList = classList;
_index = -1;
}
#region IEnumerator Members
public void Reset()
{
_index = -1;
}
public object Current
{
get
{
return _classList._students[_index];
}
}
public bool MoveNext()
{
_index++;
if (_index >= _classList._students.Count)
return false;
else
return true;
}
#endregion
}
#endregion
}
public class MyClass
{
public static void Main()
{
ClassList myClass = new ClassList("History 204");
Console.WriteLine("Class: {0}, Total Students: {1}\n",
myClass.ID, myClass.TotalStudents.ToString());
foreach (Student student in myClass)
Console.WriteLine(student);
Console.ReadLine();
}
}
--------------------------------------------------------------------------------
注意到,ClassList在实现IEnumerable的GetEnumerator()方法时,有两个选择:调用(cheat)字段ArrayList的GetEnumerator(),或者自行创建ClassEnumerator,再操作。这可能就是“*ator”与“*able”成对出现的原因:灵活性!
这种灵活性体现在,对于外界有一个统一的“*able”接口提供调用,对于内部实现,可以调用(cheat)其他现成的“*ator”,或者自己实现;但“*able”作为规范是必须实现的!
这其实是一个二次规范:“*able”作为必备实现接口,一级规范;“*ator”作为可选实现接口,二级规范。将接口的功能进行了“二次切割”,确实不同凡响。





}
}







