[摘要]这一课讲述如何在C#的类中声明索引,以使类能象数组一样被访问,这样类的实例就能够使用数组访问操作符[]来访问类对象.在C#中定义索引和在C++中定义操作符[]类似.对于那些封装了数组或者使用起来有点象集合的类,就可以使用索引,这样用户就能够使用访问数组的语法访问这个类.举个例子,假定,你想要定义一...
这一课讲述如何在C#的类中声明索引,以使类能象数组一样被访问,这样类的实例就能够使用
数组访问操作符[]来访问类对象.
在C#中定义索引和在C++中定义操作符[]类似.对于那些封装了数组或者使用起来有点象集合
的类,就可以使用索引,这样用户就能够使用访问数组的语法访问这个类.
举个例子,假定,你想要定义一个类,它使得文件就像一个字节数组一样.如果文件非常大,把
整个文件都读入内存是不切合实际的,尤其是在你只想读写其中一小部分字节的时候更是如
此.这里定义了一个类FileByteArray,使文件看起来就像一个数组一样,但是实际上只有在
字节读写的时候才会进行文件输入输出操作.
下面给出了如何定义一个索引属性.
例子
在这个例子中,FileByteArray使得对文件的访问像字节数组一样. Reverse类把文件的字节
颠倒过来.你可以就那下面这个程序本身试验一下,执行两次就恢复原状了.
000: // Indexers\indexer.cs
001: using System;
002: using System.IO;
003:
004: // Class to provide access to a large file
005: // as if it were a byte array.
006: public class FileByteArray
007: {
008: Stream stream;// Holds the underlying stream
009: // used to access the file.
010: // Create a new FileByteArray encapsulating a particular file.
011: public FileByteArray(string fileName)
012: {
013: stream = new FileStream(fileName, FileMode.Open);
014: }
015:
016: // Close the stream. This should be the last thing done
017: // when you are finished.
018: public void Close()
019: {
020: stream.Close();
021: stream = null;
022: }
023:
024: // Indexer to provide read/write access to the file.
025: public byte this[long index] // long is a 64-bit integer
026: {
027: // Read one byte at offset index and return it.
028: get
029: {
030: byte[] buffer = new byte[1];
031: stream.Seek(index, SeekOrigin.Begin);
032: stream.Read(buffer, 0, 1);
033: return buffer[0];
034: }
035: // Write one byte at offset index and return it.
036: set
037: {
038: byte[] buffer = new byte[1] {value};
039: stream.Seek(index, SeekOrigin.Begin);
040: stream.Write(buffer, 0, 1);
041: }
042: }
043:
044: // Get the total length of the file.
045: public long Length
046: {
047: get {
048: return stream.Seek(0, SeekOrigin.End);
049: }
050: }
051: }
052:
053: // Demonstrate the FileByteArray class.
054: // Reverses the bytes in a file.
055: public class Reverse
056: {
057: public static void Main(String[] args)
058: {
059: // Check for arguments.
060: if (args.Length == 0)
061: {
062: Console.WriteLine("indexer ");
063: return;
064: }
065:
066: FileByteArray file = new FileByteArray(args[0]);
067: long len = file.Length;
068:
069: // Swap bytes in the file to reverse it.
070: for (long i = 0; i < len / 2; ++i)
071: {
072: byte t;
073:
074: // Note that indexing the "file" variable invokes the
075: // indexer on the FileByteStream class, which reads
076: // and writes the bytes in the file.
077: t = file[i];
078: file[i] = file[len - i - 1];
079: file[len - i - 1] = t;
080: }
081:
082: file.Close();
083: }
084: }
运行结果
用下面的文本文件测试这个程序.
// Indexers\Test.txt
public class Hello1
{
public static void Main()
{
System.Console.WriteLine("Hello, World!");
}
}
编译并运行程序如下:
indexer Test.txt
type Test.txt
将会产生如下的显示:
}
}
;)"!dlroW ,olleH"(eniLetirW.elosnoC.metsyS
{
)(niaM diov citats cilbup
{
1olleH ssalc cilbup
txt.tseT\srexednI //
[代码讨论]
* 因为索引使用操作符[],所以注意在声明的时候使用关键字this,而没有名字.
* 上面的例子中,定义了一个下标是长整数,返回值是字节的索引,在get中定义了代码从一个
文件中读取一个字节,set中定义了代码往一个文件中写入一个字节.
* 一个索引至少要有一个参数.有时候还可以定义多个参数,象一个多维虚拟数组一样,但是这
种情况非常少见. 另外,尽管整型参数是最常见的,但是索引的参数可以是任何类型.标准的
字典类就提供了一个参数是object的索引.
* 尽管索引是一个非常强有力的特性,但是,只有在使用数组形式的访问有确切的含义时才是合
适的. 例如下面就是一个不恰当的例子.
class Employee
{
// VERY BAD STYLE: using an indexer to access
// the salary of an employee.
public double this[int year]
{
get
{
// return employee's salary for a given year.
}
}
}
仔细体会一下.
* 索引既可以被重载(Overload),也可以被覆盖(Override).(以后详细讨论)
[高级话题]
如何创建一个"索引属性"(Indexed Property)?
有的时候,一个类从不同的角度看,可能可以看成不同种类的集合. 一种叫做索引属性的技术
就可以使这种对象得到实现.
简单的说, 从字面上,我们可以理解,索引属性,首先是一个属性域,其次,它也是一个索引.举个
例子,假设你想写一个类Document,用来封装一段文本,目的是为了能够方便地检查拼写,这样你
可以把这段文本看成多个单词的数组或者是多条语句的数组.最简单地,你可能想要按如下方式
写检查拼写的代码,
Document d = new Document();
// ...
for (int i = 0; i < d.Words.Count; ++i)
{
if (d.Words[i] == "Peter")
d.Words[i] = "Joe";
}
for (int i = 0; i < d.Sentences.Count; ++i)
{
if (d.Sentences[i] == "Elvis is the king.")
d.Sentences[i] = "Eric Clapton is a guitar god.";
}
下面的代码给出如何实现这样一个类.为了实现索引属性,你应该注意到,这段代码定义了一个
嵌套类,在嵌套类的内部包含了对主类实例的引用.在主类中定义了只读的域,用于访问嵌套类
所定义的"虚拟数组",这两个域就是索引属性.
public class Document
{
public struct WordCollection
{
readonly Document document;// The containing document
internal WordCollection(Document d)
{
document = d;
}
public string this[int indexer]
{
get
{
return document.GetNthWord(indexer);
}
set
{
document.SetNthWord(indexer, value);
}
}
public int Count
{
get
{
return document.CountWords();
}
}
}
public struct SentenceCollection
{
readonly Document document;// The containing document
internal SentenceCollection(Document d)
{
document = d;
}
public string this[int indexer] {
get
{
return document.GetNthSentence(indexer);
}
set
{
document.SetNthSentence(indexer, value);
}
}
public int Count
{
get
{
return document.CountSentences();
}
}
}
// Because the types of the fields have indexers,
// these fields appear as "indexed properties"
public readonly WordCollection Words;
public readonly SentenceCollection Sentences;
public Document()
{
Words = new WordCollection(this);
Sentences = new SentenceCollection(this);
}
private string GetNthWord(int index)
{
/* ... */
return "";
}
private void SetNthWord(int index, string w)
{
/* ... */
}
private int CountWords()
{
/* ... */
return 0;
}
private string GetNthSentence(int index)
{
/* ... */
return "";
}
private void SetNthSentence(int index, string s)
{
/* ... */
}
private int CountSentences()
{
/* ... */
return 0;
}
}
注意: 要谨慎地使用这种技术!不能乱用.只有当数组抽象具有特定的含义,而且能够使你的代码
更加清晰的时候,才应该使用索引或者索引属性.
……