這一課講述如何在C#的類中聲明索引,以使類能象數(shù)組一樣被訪問,這樣類的實(shí)例就能夠使用 數(shù)組訪問操作符[]來訪問類對象. 在C#中定義索引和在C++中定義操作符[]類似.對于那些封裝了數(shù)組或者使用起來有點(diǎn)象集合 的類,就可以使用索引,這樣用戶就能夠使用訪問數(shù)組的語法訪問這個(gè)類. 舉個(gè)例子,假定,你想要定義一個(gè)類,它使得文件就像一個(gè)字節(jié)數(shù)組一樣.如果文件非常大,把 整個(gè)文件都讀入內(nèi)存是不切合實(shí)際的,尤其是在你只想讀寫其中一小部分字節(jié)的時(shí)候更是如 此.這里定義了一個(gè)類FileByteArray,使文件看起來就像一個(gè)數(shù)組一樣,但是實(shí)際上只有在 字節(jié)讀寫的時(shí)候才會進(jìn)行文件輸入輸出操作.
下面給出了如何定義一個(gè)索引屬性.
例子
在這個(gè)例子中,FileByteArray使得對文件的訪問像字節(jié)數(shù)組一樣. Reverse類把文件的字節(jié) 顛倒過來.你可以就那下面這個(gè)程序本身試驗(yàn)一下,執(zhí)行兩次就恢復(fù)原狀了.
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: }
運(yùn)行結(jié)果
用下面的文本文件測試這個(gè)程序.
// Indexers\Test.txt public class Hello1 { public static void Main() { System.Console.WriteLine("Hello, World!"); } }
編譯并運(yùn)行程序如下:
indexer Test.txt type Test.txt
將會產(chǎn)生如下的顯示:
} } ;)"!dlroW ,olleH"(eniLetirW.elosnoC.metsyS { )(niaM diov citats cilbup { 1olleH ssalc cilbup txt.tseT\srexednI //
[代碼討論]
* 因?yàn)樗饕褂貌僮鞣鸞],所以注意在聲明的時(shí)候使用關(guān)鍵字this,而沒有名字. * 上面的例子中,定義了一個(gè)下標(biāo)是長整數(shù),返回值是字節(jié)的索引,在get中定義了代碼從一個(gè) 文件中讀取一個(gè)字節(jié),set中定義了代碼往一個(gè)文件中寫入一個(gè)字節(jié). * 一個(gè)索引至少要有一個(gè)參數(shù).有時(shí)候還可以定義多個(gè)參數(shù),象一個(gè)多維虛擬數(shù)組一樣,但是這 種情況非常少見. 另外,盡管整型參數(shù)是最常見的,但是索引的參數(shù)可以是任何類型.標(biāo)準(zhǔn)的 字典類就提供了一個(gè)參數(shù)是object的索引. * 盡管索引是一個(gè)非常強(qiáng)有力的特性,但是,只有在使用數(shù)組形式的訪問有確切的含義時(shí)才是合 適的. 例如下面就是一個(gè)不恰當(dāng)?shù)睦?
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. } } }
仔細(xì)體會一下.
* 索引既可以被重載(Overload),也可以被覆蓋(Override).(以后詳細(xì)討論)
[高級話題] 如何創(chuàng)建一個(gè)"索引屬性"(Indexed Property)?
有的時(shí)候,一個(gè)類從不同的角度看,可能可以看成不同種類的集合. 一種叫做索引屬性的技術(shù) 就可以使這種對象得到實(shí)現(xiàn). 簡單的說, 從字面上,我們可以理解,索引屬性,首先是一個(gè)屬性域,其次,它也是一個(gè)索引.舉個(gè) 例子,假設(shè)你想寫一個(gè)類Document,用來封裝一段文本,目的是為了能夠方便地檢查拼寫,這樣你 可以把這段文本看成多個(gè)單詞的數(shù)組或者是多條語句的數(shù)組.最簡單地,你可能想要按如下方式 寫檢查拼寫的代碼,
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."; }
下面的代碼給出如何實(shí)現(xiàn)這樣一個(gè)類.為了實(shí)現(xiàn)索引屬性,你應(yīng)該注意到,這段代碼定義了一個(gè) 嵌套類,在嵌套類的內(nèi)部包含了對主類實(shí)例的引用.在主類中定義了只讀的域,用于訪問嵌套類 所定義的"虛擬數(shù)組",這兩個(gè)域就是索引屬性.
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; } }
注意: 要謹(jǐn)慎地使用這種技術(shù)!不能亂用.只有當(dāng)數(shù)組抽象具有特定的含義,而且能夠使你的代碼 更加清晰的時(shí)候,才應(yīng)該使用索引或者索引屬性.
|