这一课讲述如何在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; } }
注意: 要谨慎地使用这种技术!不能乱用.只有当数组抽象具有特定的含义,而且能够使你的代码 更加清晰的时候,才应该使用索引或者索引属性.
|