在 foreach 循环中枚举 XML 数据






4.91/5 (21投票s)
2004年10月6日
3分钟阅读

100669

892
本文展示了如何在 foreach 循环中枚举 XML 数据,就像数据在一个集合中一样。
引言
本文演示了如何使用 foreach
循环遍历 XML 文件中的数据,就像数据存储在一个对象集合中一样。 这里介绍的基本概念是 .NET 的 IEnumerable
/IEnumerator
机制不一定必须与集合类一起使用。
背景
假设你有一个 XML 文件,其中包含一些你想向用户展示的数据。 有很多种方法可以将数据从 XML 文件获取到输出设备。 许多技术都涉及将整个文件读入某个数据结构,然后显示缓存数据的某个子集。 如果你可以直接“遍历 XML 文件”本身,并且只存储你想显示的值,那岂不是很好? 如果你可以使用一个简单的 foreach
循环来执行此迭代,那岂不是更好? 幸运的是,.NET Framework 允许通过 IEnumerable
和 IEnumerator
接口实现这种类型的灵活性和优雅性。
在我们查看代码之前,这里有一个关于 foreach
循环如何操作的复习。 要迭代的对象必须实现 IEnumerable
接口。 IEnumerable
有一个方法 GetEnumerator()
,它返回一个实现 IEnumerator
接口的对象。 IEnumerator
有三个公共成员
- 一个名为
Current
的属性,它返回枚举值列表中的“当前”项。
- 一个名为
MoveNext()
的方法,它将枚举器移动到值列表中的“下一个”项,如果没有更多的项可以迭代,则返回false
。 - 一个名为
Reset()
的方法,它将枚举器定位到值列表中的“第一个”项之前。
在 foreach
机制通过 Current
属性请求当前项之前,它将调用 MoveNext()
方法。 假设 MoveNext()
返回 true
,则调用 Current
,并且其返回值成为 foreach
循环范围内的“局部临时”变量。
使用代码
本文随附的示例项目非常简单。 它的目的是演示所提出的技术,但它可以很容易地扩展以适应更复杂的需求。 该示例包含一个包含客户信息的 XML 文件,具有一个非常简单的模式
CUSTOMERS.XML
<?xml version="1.0" encoding="utf-8" ?>
<Customers>
<Customer id="1">
<FirstName>Abe</FirstName>
<LastName>Lalice</LastName>
<Orders>7</Orders>
<Balance>340.95</Balance>
</Customer>
<Customer id="2">
<FirstName>Mary</FirstName>
<LastName>Poolsworth</LastName>
<Orders>14</Orders>
<Balance>3782.02</Balance>
</Customer>
<Customer id="3">
<FirstName>Perth</FirstName>
<LastName>Higgins</LastName>
<Orders>1</Orders>
<Balance>42.00</Balance>
</Customer>
<Customer id="4">
<FirstName>David</FirstName>
<LastName>Applegate</LastName>
<Orders>2</Orders>
<Balance>232.50</Balance>
</Customer>
<Customer id="5">
<FirstName>Martha</FirstName>
<LastName>Whithersby</LastName>
<Orders>26</Orders>
<Balance>19023.07</Balance>
</Customer>
</Customers>
以下代码从 XML 文件中获取某些客户并将它们放入 ListBox
控件中。 这样做的好处是,从客户端(Customers
类的用户)的角度来看,无法知道数据是从 XML 文件中实时读取的。 这种实现方式被完全封装起来。
private void CustomerEnumerationForm_Load(object sender,
System.EventArgs e)
{
//
// Here we create a Customers object and implicitly
// use the CustomerEnumerator to iterate
// over all of the customers in the XML file.
// From our perspective here, it seems
// as though the Customers class has a collection
// containing all of the customers. In
// reality, though, the CustomerEnumerator is reading
// in each customer from Customers.xml
// one at a time, as they are requested by the foreach loop.
//
//
// Note, Customers implements IDisposable.
// This is necessary in case the foreach loop is
// exited before iterating over all of the customers
// in the file. You do not have to explicitly
// invoke the Dispose method, however, because a finalizer
// is in place to handle disposing of the
// XMLTextReader if the client code does not.
using( Customers customers = new Customers() )
{
foreach( Customer cust in customers )
if( cust.Orders > 2 )
this.lstCustomers.Items.Add( cust );
}
}
Customers
类表示文件中的所有客户。
public class Customers : IEnumerable
{
// When the foreach loop begins, this method is invoked
// so that the loop gets an enumerator to query.
public IEnumerator GetEnumerator()
{
return new CustomerEnumerator();
}
}
CustomerEnumerator
类包含从 XML 文件中读取客户数据的代码。
// Exposes the customer data found in Customers.xml.
public class CustomerEnumerator : IEnumerator
{
// Data
private readonly string fileName = @"..\..\Customers.xml";
private XmlTextReader reader;
// IEnumerator Members
// Called when the enumerator needs to be reinitialized.
public void Reset()
{
if( this.reader != null )
this.reader.Close();
System.Diagnostics.Debug.Assert( File.Exists( this.fileName ),
"Customer file does not exist!" );
StreamReader stream = new StreamReader( this.fileName );
this.reader = new XmlTextReader( stream );
}
// Called just prior to Current being invoked.
// If true is returned then the foreach loop will
// try to get another value from Current.
// If false is returned then the foreach loop terminates.
public bool MoveNext()
{
// Call Reset the first time MoveNext is called
// instead of in the constructor
// so that we keep the stream open only as long as needed.
if( this.reader == null )
this.Reset();
if( this.FindNextTextElement() )
return true;
// If there are no more text elements in the XML file then
// we have read in all of the data
// and the foreach loop should end.
this.reader.Close();
return false;
}
// Invoked every time MoveNext() returns true.
// This extracts the values for the "current" customer from
// the XML file and returns that data packaged up as a Customer object.
public object Current
{
get
{
// No need to call FindNextTextElement here
// because it was called for us by MoveNext().
string firstName = this.reader.Value;
// Set 'val' to a default value in case the element was empty.
string val = "";
if( this.FindNextTextElement() )
val = this.reader.Value;
string lastName = val;
// Set 'val' to a default value in case the element was empty.
val = "0";
if( this.FindNextTextElement() )
val = this.reader.Value;
int orders;
try { orders = Int32.Parse( val ); }
catch { orders = Int32.MinValue; }
// Set 'val' to a default value in case the element was empty.
val = "0";
if( this.FindNextTextElement() )
val = this.reader.Value;
decimal balance;
try { balance = Decimal.Parse( val ); }
catch { balance = Decimal.MinValue; }
return new Customer( firstName, lastName, orders, balance );
}
}
// Private Helper
// Advances the XmlTextReader to the next Text
// element in the XML stream.
// Returns true if there is more data to be read
// from the stream, else false.
private bool FindNextTextElement()
{
bool readOn = this.reader.Read();
bool prevTagWasElement = false;
while( readOn && this.reader.NodeType != XmlNodeType.Text )
{
// If the current element is empty, stop reading and return false.
if( prevTagWasElement && this.reader.NodeType == XmlNodeType.EndElement )
readOn = false;
prevTagWasElement = this.reader.NodeType == XmlNodeType.Element;
readOn = readOn && this.reader.Read();
}
return readOn;
}
}
有一个非常简单的 Customer
类,它只是保存从 XML 文件中提取的数据,以便枚举器可以捆绑有关单个客户的所有数据。 我不会在这里展示它,因为该类中没有任何与本文主题相关的内容。 不过,它在示例项目中可用。
结论
虽然这个新颖的想法可能不是软件开发的下一个银弹,但我希望你觉得它有用或至少有趣。 我知道我是这么认为的,否则我就不会费心写一篇关于它的文章了!:)
更新
- 2005年1月9日 - 修复了
FindNextTextElement()
以处理元素不包含TextElement
的情况。 还在Customers
和CustomerEnumerator
上实现了IDisposable
,并向CustomerEnumerator
添加了终结器。 所有这些方法都是为CustomerEnumerator
的用户在遍历文件中的所有元素之前从foreach
循环中断开的情况添加的。 需要有一种方法来关闭XmlTextReader
。