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