这一课讲述怎样实现一个集合类,以及怎样用foreach对其访问.前面讲过,foreach语句
是迭代访问数组的非常方便的方法,它也可以用来枚举集合中的元素,这是因为集合类 F[} Bg
实现了System.Collections.IEnumerator和System.Coolections.IEnumerable两个接口
. 1A6s9QVCyyk
例1
下面的类是用于分析字符串中的单词,和C运行时库中的strtok函数很类似。
000: // CollectionClasses/tokens.cs ??Za[AB
001: using System; /!]|!B Lt?4p
002: using System.Collections;
003:
004: public class Tokens : IEnumerable )Fz:y6Sb p8Z$_+c
005: {
/y IfDN/
006: private string[] elements; n.cL{+WV
007:
008: Tokens(string source, char[] delimiters)
009: { r2f-Z$}(O;WI z ^H
010: elements = source.Split(delimiters);
011: }
012: e%V
K/t
~mix
013: // IEnumerable Interface Implementation 7A^L;XvD
J
014: fE}1H KB1pX
015: public IEnumerator GetEnumerator()
016: {
017: return new TokenEnumerator(this); pA/K,E{_&N7U#?
018: }
019:
020: // Inner class implements IEnumerator interface 8?%Txq^S/o9q(p
021:
022: private class TokenEnumerator : IEnumerator
023: { bZ!K.^UMt
024: private int position = -1;
025: private Tokens t;
026:
027: public TokenEnumerator(Tokens t)
028: { 9ky/m k4y0B2bR
029: this.t = t;
030: }
031:
032: public bool MoveNext() 5_L^!U`#j%KG
033: {
034: if (position < t.elements.Length - 1) M!W7E!h c|
035: { 8K:Ec(M)q+X
036: position++; 2h {8ab%_rt{!A
YD*e
037: return true;
038: } DPM'x
o&DS4/
039: else CC,d)u9g8S'zy8B0zYw
040: { 6Z4[4lT(KRZ1p.s
041: return false;
042: }
043: } j%Mt!EB-d$D
g&L
044: N HCr
{2[
045: public void Reset()
046: {
047: position = -1;
048: } 1E|~b+e^^,FH].V
049:
dut)bp1{t%E
050: public object Current
051: {
052: get 9`X*w1Q,t f!]?DNe
053: { 6L6B@'V4V7b
054: return t.elements[position];
055: } j K(U7~l9@lu4W^
056: } JT%f-S^;K
057: }
058:
059: // Test Tokens, TokenEnumerator
060: x
hh k i
061: static void Main() #Pz1ekK_$` D5t
062: {
063: Tokens f = new Tokens("This is a well-done program.",
new char[] {' ','-'});
064: foreach (string item in f)
065: { Q6s;k!{I/@
066: Console.WriteLine(item); I'Kb`"oY#V6w
067: } Tt9u lh1gu+?9G
068: }
069: } v
m/tP`ls
g
输出结果
This ol_&J'Cj+v)LB/
is /YX4ckwjJ
a
/6Fy[$nD6?P
well
~xI n[
JC:l T
done
program.
代码解释
* 第1行,编译指示使用.NET框架的System名字空间 ^%/T!ZF
* 第2行,编译指示使用.NET框架的System.Collection名字空间 v3A"vo)} V;YN,xl
* 第4行,声明Tokens类 %kv2CLX2`;F
* 第10行,使用方法Split把字符串分割成单词
* 第15行,声明了由接口IEnumerable所要求的GetEnumerator函数,返回一个 .Vh cki} ]c
TokenEmumerator对象
* 第22行,声明了一个内部类TokenEnumerator,它实现了IEnumerator接口 ,|*MJ#u(a2t {J%e
* 第32行,声明了接口IEnumerator要求的MoveNext函数 X(]%J]CwU
Z r
* 第45行,声明了接口IEnumerator要求的Reset函数 @o]h/0q
* 第50行,声明了接口IEnumerator要求的Current函数
* 第63行到第67行,测试。输入参数"This is a well-done program",把它按照 l-/a$?YWj K
' '和'-'分割。并且用foreach语句对其枚举
注意:这个例子中Token类在内部使用了数组,它本身就缺省实现了IEnumerator和 -]T/A0XQ[#s:]dt
IEnumerable,我们当然可以直接使用数组的枚举方法。但是,这样就失去了我们 xTK2apu]/vhs
举这个例子的目的。
另外,在C#中要想使用foreach语句访问集合,并不一定非要实现IEnumerable和 eD DJ}9i:SHV#I
IEnumerator.只要在这个类中实现了所要求的GetNumerator,MoveNext,Reset和Current
成员,
就可以使用foreach了。不是用接口的一个好处是,可以让Current函数返回更明确的
类型,而不是Object,这样就保证了类型安全。
举个例子,把上面的代码稍作修改如下, G|RM4q x-X
004: public class Tokens // no longer inherits from IEnumerable 4/i Ju
PUaP~9L
015: public TokenEnumerator GetEnumerator() // doesn't return an IEnumerator
022: public class TokenEnumerator // no longer inherits from IEnumerator 4|$}6L#p+JK[
050: public string Current // type-safe: returns string, not object "s2YgA{ n"T,b
现在,因为Current返回一个字符串,就能够在编译时刻检查出在foreach语句种 LktX7y
类型不匹配的错误。 :{%nS'im]sM|_ Lq
064: foreach (int item in f) // Error: cannot convert string to int
但是,不使用IEnumerable和IEnumberator接口的缺点就是,不再能够和其它.Net语言的 0yrJ3QJyF A)i6g'K
foreach语句(或者其它等价的语句)混合使用.
因此,你可以在C#提供的
类型安全和与其它的.Net语言交互访问之中选择其一. !S+x4G#Z3F'] ]2k Wa
下面的例子给出继承IEnumerable和IEnumerator,以及怎样显示实现.
例2 0{/Je?:/PUW ^
这个例子与例1的功能相同,但是它提供了C#给予的类型安全特性,同时又保持了和
其它语言的交互访问特性。
000: // CollectionClasses/tokens2.cs
001: using System;
002: using System.Collections; J-Q"?C3l
f8n3w4L
003:
004: public class Tokens: IEnumerable F$V,X
O#nK
^*C
005: { 6G~%s-] SJ9q#V0D
006: private string[] elements;
007:
008: Tokens(string source, char[] delimiters)
009: { J}Xp6`:B%E"W2}
w&w9mY
010: elements = source.Split(delimiters); _a&q2m/
011: }
012: p"~4NP:g2E{ d"t
013: // IEnumerable Interface Implementation
014: +v4P4t%Ql
015: public TokenEnumerator GetEnumerator() // non-IEnumerable version
016: {
017: return new TokenEnumerator(this);
018: }
019:
pK,@z}J
020: IEnumerator IEnumerable.GetEnumerator() // IEnumerable version
021: {
022: return (IEnumerator) new TokenEnumerator(this); a q??F-C
023: } R}fd
YUt
{
024:
025: // Inner class implements IEnumerator interface #HA'LmM3m"R
026:
027: public class TokenEnumerator: IEnumerator #sb5YQ{/x#Co
028: { Pg-DyY7c?
029: private int position = -1;
030: private Tokens t;
031: [4NvR,^ X(GH
032: public TokenEnumerator(Tokens t)
033: {
034: this.t = t;
035: }
036:
037: public bool MoveNext()
038: { ~ S2Un,a [
W
039: if (position < t.elements.Length - 1) `HT p.l1C7? Z-l
040: { z0jFQ;@)?E
041: position++; j{P2Y8^7`'j
042: return true; 7syc}/J N9i]-Z
043: }
~6a'QUP:z6z D
044: else
045: { j#Qagt5t9r4u
046: return false;
047: } zh'/j+bc1F:/
048: } k
K?;U/X
049:
050: public void Reset()
051: { "w}P
G'n
052: position = -1; HL'Lo5Y6q-_V
053: } &Y7@MT"x
s'Aq
d+q
054: ,k0Y*yp kh)f
055: public string Current // non-IEnumerator version: type-safe
056: { Z:_w7TW
057: get
058: { zwC0rsBs s[
059: return t.elements[position];
060: } yj(x,}c
061: }
062:
063: object IEnumerator.Current // IEnumerator version: returns object
064: {
065: get
066: {
067: return t.elements[position]; !rV}#uYZ2K6T"h
068: }
069: } M/Xje
/8j7y
070: }
071: 'D2}
kSv8g/vd
072: // Test Tokens, TokenEnumerator $q0W)v3[:f
073:
074: static void Main()
075: {
Z4nzp0G
076: Tokens f = new Tokens("This is a well-done program.", '`B#}U3A,J!g
new char [] {' ','-'}); B*T p1R]Jzk,rt'G
077: foreach (string item in f) // try changing string to int n"x3~&l H`l_
078: {
079: Console.WriteLine(item); 0J!JoANH1s k
Q
080: } }A$D3z(hmLI
081: }
082: }
是迭代访问数组的非常方便的方法,它也可以用来枚举集合中的元素,这是因为集合类 F[} Bg
实现了System.Collections.IEnumerator和System.Coolections.IEnumerable两个接口
. 1A6s9QVCyyk
例1
下面的类是用于分析字符串中的单词,和C运行时库中的strtok函数很类似。
000: // CollectionClasses/tokens.cs ??Za[AB
001: using System; /!]|!B Lt?4p
002: using System.Collections;
003:
004: public class Tokens : IEnumerable )Fz:y6Sb p8Z$_+c
005: {
/y IfDN/
006: private string[] elements; n.cL{+WV
007:
008: Tokens(string source, char[] delimiters)
009: { r2f-Z$}(O;WI z ^H
010: elements = source.Split(delimiters);
011: }
012: e%V
K/t
~mix
013: // IEnumerable Interface Implementation 7A^L;XvD
J
014: fE}1H KB1pX
015: public IEnumerator GetEnumerator()
016: {
017: return new TokenEnumerator(this); pA/K,E{_&N7U#?
018: }
019:
020: // Inner class implements IEnumerator interface 8?%Txq^S/o9q(p
021:
022: private class TokenEnumerator : IEnumerator
023: { bZ!K.^UMt
024: private int position = -1;
025: private Tokens t;
026:
027: public TokenEnumerator(Tokens t)
028: { 9ky/m k4y0B2bR
029: this.t = t;
030: }
031:
032: public bool MoveNext() 5_L^!U`#j%KG
033: {
034: if (position < t.elements.Length - 1) M!W7E!h c|
035: { 8K:Ec(M)q+X
036: position++; 2h {8ab%_rt{!A
YD*e
037: return true;
038: } DPM'x
o&DS4/
039: else CC,d)u9g8S'zy8B0zYw
040: { 6Z4[4lT(KRZ1p.s
041: return false;
042: }
043: } j%Mt!EB-d$D
g&L
044: N HCr
{2[
045: public void Reset()
046: {
047: position = -1;
048: } 1E|~b+e^^,FH].V
049:
dut)bp1{t%E
050: public object Current
051: {
052: get 9`X*w1Q,t f!]?DNe
053: { 6L6B@'V4V7b
054: return t.elements[position];
055: } j K(U7~l9@lu4W^
056: } JT%f-S^;K
057: }
058:
059: // Test Tokens, TokenEnumerator
060: x
hh k i
061: static void Main() #Pz1ekK_$` D5t
062: {
063: Tokens f = new Tokens("This is a well-done program.",
new char[] {' ','-'});
064: foreach (string item in f)
065: { Q6s;k!{I/@
066: Console.WriteLine(item); I'Kb`"oY#V6w
067: } Tt9u lh1gu+?9G
068: }
069: } v
m/tP`ls
g
输出结果
This ol_&J'Cj+v)LB/
is /YX4ckwjJ
a
/6Fy[$nD6?P
well
~xI n[
JC:l T
done
program.
代码解释
* 第1行,编译指示使用.NET框架的System名字空间 ^%/T!ZF
* 第2行,编译指示使用.NET框架的System.Collection名字空间 v3A"vo)} V;YN,xl
* 第4行,声明Tokens类 %kv2CLX2`;F
* 第10行,使用方法Split把字符串分割成单词
* 第15行,声明了由接口IEnumerable所要求的GetEnumerator函数,返回一个 .Vh cki} ]c
TokenEmumerator对象
* 第22行,声明了一个内部类TokenEnumerator,它实现了IEnumerator接口 ,|*MJ#u(a2t {J%e
* 第32行,声明了接口IEnumerator要求的MoveNext函数 X(]%J]CwU
Z r
* 第45行,声明了接口IEnumerator要求的Reset函数 @o]h/0q
* 第50行,声明了接口IEnumerator要求的Current函数
* 第63行到第67行,测试。输入参数"This is a well-done program",把它按照 l-/a$?YWj K
' '和'-'分割。并且用foreach语句对其枚举
注意:这个例子中Token类在内部使用了数组,它本身就缺省实现了IEnumerator和 -]T/A0XQ[#s:]dt
IEnumerable,我们当然可以直接使用数组的枚举方法。但是,这样就失去了我们 xTK2apu]/vhs
举这个例子的目的。
另外,在C#中要想使用foreach语句访问集合,并不一定非要实现IEnumerable和 eD DJ}9i:SHV#I
IEnumerator.只要在这个类中实现了所要求的GetNumerator,MoveNext,Reset和Current
成员,
就可以使用foreach了。不是用接口的一个好处是,可以让Current函数返回更明确的
类型,而不是Object,这样就保证了类型安全。
举个例子,把上面的代码稍作修改如下, G|RM4q x-X
004: public class Tokens // no longer inherits from IEnumerable 4/i Ju
PUaP~9L
015: public TokenEnumerator GetEnumerator() // doesn't return an IEnumerator
022: public class TokenEnumerator // no longer inherits from IEnumerator 4|$}6L#p+JK[
050: public string Current // type-safe: returns string, not object "s2YgA{ n"T,b
现在,因为Current返回一个字符串,就能够在编译时刻检查出在foreach语句种 LktX7y
类型不匹配的错误。 :{%nS'im]sM|_ Lq
064: foreach (int item in f) // Error: cannot convert string to int
但是,不使用IEnumerable和IEnumberator接口的缺点就是,不再能够和其它.Net语言的 0yrJ3QJyF A)i6g'K
foreach语句(或者其它等价的语句)混合使用.
因此,你可以在C#提供的
类型安全和与其它的.Net语言交互访问之中选择其一. !S+x4G#Z3F'] ]2k Wa
下面的例子给出继承IEnumerable和IEnumerator,以及怎样显示实现.
例2 0{/Je?:/PUW ^
这个例子与例1的功能相同,但是它提供了C#给予的类型安全特性,同时又保持了和
其它语言的交互访问特性。
000: // CollectionClasses/tokens2.cs
001: using System;
002: using System.Collections; J-Q"?C3l
f8n3w4L
003:
004: public class Tokens: IEnumerable F$V,X
O#nK
^*C
005: { 6G~%s-] SJ9q#V0D
006: private string[] elements;
007:
008: Tokens(string source, char[] delimiters)
009: { J}Xp6`:B%E"W2}
w&w9mY
010: elements = source.Split(delimiters); _a&q2m/
011: }
012: p"~4NP:g2E{ d"t
013: // IEnumerable Interface Implementation
014: +v4P4t%Ql
015: public TokenEnumerator GetEnumerator() // non-IEnumerable version
016: {
017: return new TokenEnumerator(this);
018: }
019:
pK,@z}J
020: IEnumerator IEnumerable.GetEnumerator() // IEnumerable version
021: {
022: return (IEnumerator) new TokenEnumerator(this); a q??F-C
023: } R}fd
YUt
{
024:
025: // Inner class implements IEnumerator interface #HA'LmM3m"R
026:
027: public class TokenEnumerator: IEnumerator #sb5YQ{/x#Co
028: { Pg-DyY7c?
029: private int position = -1;
030: private Tokens t;
031: [4NvR,^ X(GH
032: public TokenEnumerator(Tokens t)
033: {
034: this.t = t;
035: }
036:
037: public bool MoveNext()
038: { ~ S2Un,a [
W
039: if (position < t.elements.Length - 1) `HT p.l1C7? Z-l
040: { z0jFQ;@)?E
041: position++; j{P2Y8^7`'j
042: return true; 7syc}/J N9i]-Z
043: }
~6a'QUP:z6z D
044: else
045: { j#Qagt5t9r4u
046: return false;
047: } zh'/j+bc1F:/
048: } k
K?;U/X
049:
050: public void Reset()
051: { "w}P
G'n
052: position = -1; HL'Lo5Y6q-_V
053: } &Y7@MT"x
s'Aq
d+q
054: ,k0Y*yp kh)f
055: public string Current // non-IEnumerator version: type-safe
056: { Z:_w7TW
057: get
058: { zwC0rsBs s[
059: return t.elements[position];
060: } yj(x,}c
061: }
062:
063: object IEnumerator.Current // IEnumerator version: returns object
064: {
065: get
066: {
067: return t.elements[position]; !rV}#uYZ2K6T"h
068: }
069: } M/Xje
/8j7y
070: }
071: 'D2}
kSv8g/vd
072: // Test Tokens, TokenEnumerator $q0W)v3[:f
073:
074: static void Main()
075: {
Z4nzp0G
076: Tokens f = new Tokens("This is a well-done program.", '`B#}U3A,J!g
new char [] {' ','-'}); B*T p1R]Jzk,rt'G
077: foreach (string item in f) // try changing string to int n"x3~&l H`l_
078: {
079: Console.WriteLine(item); 0J!JoANH1s k
Q
080: } }A$D3z(hmLI
081: }
082: }