使用 C# 中的索引器和 JavaScript 中訪問對象的屬性是很相似。
之前了解過索引器,當時還把索引器和屬性給記混了, 以為索引器就是屬性,下面寫下索引器和屬性的區別,以及怎么使用索引器
先說明一點,這里的索引器和數據庫中的索引不一樣,雖然都是找元素。
索引器和屬性的區別:
- 屬性和索引器都是函數,但是表現形式不一樣;(屬性和索引器在代碼的表現形式上和函數不一致,但其本質都是函數,需要通過 ILDASM 來查看,或者使用反射)
- 索引器可以被重載,而屬性沒有重載這一說法;(索引器的重載即方括號中的類型不同)
- 索引器不能聲明為static,而屬性可以;(索引器之所以不能聲明為 static,因為其自身攜帶 this 關鍵字,需要被對象調用)
還有一點就是索引很像數組,它允許一個對象可以像數組一樣被中括號 [] 索引,但是和數組有區別,具體有:
- 數組的角標只能是數字,而索引器的角標可以是數字也可以是引用類型;
- 數組是一個引用類型的變量,而索引器是一個函數;
我在代碼中很少自己定義索引器,但是我卻經常在用它,那是因為系統自定義了很多索引器,比如 ADO.NET 中對於 DataTable 和 DataRow 等類的各種遍歷,查找,很多地方就是用的索引器,比如下面這篇博客中的代碼就使用了很多系統自定義的索引器:
DataTable的AcceptChanges()方法和DataRow的RowState屬性 (其中索引器的使用都以及注明,)
那我們如何自定索引器? 回到這篇博客第一句話,我曾經把索引器和屬性弄混過,那就說明他倆很像,看下代碼看是不是很像:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Dynamic; //這里的代碼時參照自http://www.cnblogs.com/ArmyShen/archive/2012/08/27/2659405.html的代碼片段 namespace Demo1 { public class IndexerClass { private string[] name = new string[2]; //索引器必須以this關鍵字定義,其實這個this就是類實例化之后的對象 public string this[int index] { //實現索引器的get方法 get { if (index >= 0 && index < 2) { return name[index]; } return null; } //實現索引器的set方法 set { if (index >= 0 && index < 2) { name[index] = value; } } } } class Program { static void Main(string[] args) { //索引器的使用 IndexerClass Indexer = new IndexerClass(); //“=”號右邊對索引器賦值,其實就是調用其set方法 Indexer[0] = "張三"; Indexer[1] = "李四"; //輸出索引器的值,其實就是調用其get方法 Console.WriteLine(Indexer[0]); Console.WriteLine(Indexer[1]); } } }
乍一眼看上去,感覺和屬性差不多了, 但是仔細一看,索引器沒有名稱,有對方括號,而且多了 this 關鍵字,看到這里的 this 的特殊用法又讓我想到了擴展方法中的 this的特殊位置。
上面再講索引和屬性的區別時,說到了索引其實也是函數,證明索引器是函數,我在上面的的代碼中加入了一個普通方法 Add,
public class IndexerClass { public string[] strArr = new string[2]; //一個屬性 public int Age { get; set; } //一個方法 public int Add(int a,int b) { return a + b; } //一個索引器 public string this[int index] { get { if (index < 2 && index >= 0) return strArr[index]; return null; } set { if (index >= 0 && index < 2) { strArr[index] = value; } } } }
我們將程序重新編譯一下,然后使用 ILDASM 工具查看下編譯出來的 .exe 文件,如下圖:
從中我們可以看到一個 Add 的方法,這個小圖標代表着方法,一共有 6 個方法,其中 .ctor 暫時不管,Add 方法是我們自己寫的,
get_Age : int32(),這個方法是屬性 age 的讀方法,對應的還有個寫方法;
還剩下兩個就是索引器生成的方法了,get_Item:string(int32) 和 set_Item : void(int32) ;
還有這樣的圖標,這個圖標是代表着字段,上面的兩個字段是自動生成的,這也是 C#中的語法糖了,不用聲明字段,只用聲明屬性,編譯器會自動生成相應字段。
關於 C# 的語法糖可以點擊這篇博客【 C# 中的語法糖 】
關於 ILDASM 的安裝與使用可以點擊這篇博客【ILDASM 的添加和使用】
以字符串為角標, 這點就和數組不一樣了:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Dynamic; using System.Collections; //這里的代碼時參照自http://www.cnblogs.com/ArmyShen/archive/2012/08/27/2659405.html的代碼片段 namespace Demo1 { public class IndexerClass { //用string作為索引器下標的時候,要用Hashtable private Hashtable name = new Hashtable(); //索引器必須以this關鍵字定義,其實這個this就是類實例化之后的對象 public string this[string index] { get { return name[index].ToString(); } set { name.Add(index, value); } } } class Program { static void Main(string[] args) { IndexerClass Indexer = new IndexerClass(); Indexer["A0001"] = "張三"; Indexer["A0002"] = "李四"; Console.WriteLine(Indexer["A0001"]); Console.WriteLine(Indexer["A0002"]); } } }
索引器的重載,這點就是屬性的不同:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Dynamic; using System.Collections; //這里的代碼時參照自http://www.cnblogs.com/ArmyShen/archive/2012/08/27/2659405.html的代碼片段 namespace Demo1 { public class IndexerClass { private Hashtable name = new Hashtable(); //1:通過key存取Values public string this[int index] { get { return name[index].ToString(); } set { name.Add(index, value); } } //2:通過Values存取key public int this[string aName] { get { //Hashtable中實際存放的是DictionaryEntry(字典)類型,如果要遍歷一個Hashtable,就需要使用到DictionaryEntry foreach (DictionaryEntry d in name) { if (d.Value.ToString() == aName) { return Convert.ToInt32(d.Key); } } return -1; } set { name.Add(value, aName); } } } class Program { static void Main(string[] args) { IndexerClass Indexer = new IndexerClass(); //第一種索引器的使用 Indexer[1] = "張三";//set訪問器的使用 Indexer[2] = "李四"; Console.WriteLine("編號為1的名字:" + Indexer[1]);//get訪問器的使用 Console.WriteLine("編號為2的名字:" + Indexer[2]); Console.WriteLine(); //第二種索引器的使用 Console.WriteLine("張三的編號是:" + Indexer["張三"]);//get訪問器的使用 Console.WriteLine("李四的編號是:" + Indexer["李四"]); Indexer["王五"] = 3;//set訪問器的使用 Console.WriteLine("王五的編號是:" + Indexer["王五"]); } } }
具有多個參數的索引器:
using System; using System.Collections; //這里的代碼時參照自http://www.cnblogs.com/ArmyShen/archive/2012/08/27/2659405.html的代碼片段 namespace Demo1 { /// <summary> /// 入職信息類 /// </summary> public class EntrantInfo { //姓名、編號、部門 public string Name { get; set; } public int Num { get; set; } public string Department { get; set; } } /// <summary> /// 聲明一個類EntrantInfo的索引器 /// </summary> public class IndexerForEntrantInfo { private ArrayList ArrLst;//用於存放EntrantInfo類 public IndexerForEntrantInfo() { ArrLst = new ArrayList(); } /// <summary> /// 聲明一個索引器:以名字和編號查找存取部門信息 /// </summary> /// <param name="name"></param> /// <param name="num"></param> /// <returns></returns> public string this[string name, int num] { get { foreach (EntrantInfo en in ArrLst) { if (en.Name == name && en.Num == num) { return en.Department; } } return null; } set { ArrLst.Add(new EntrantInfo() { Name = name, Num= num, Department = value }); } } /// <summary> /// 聲明一個索引器:以編號查找名字和部門 /// </summary> /// <param name="num"></param> /// <returns></returns> public ArrayList this[int num] { get { ArrayList temp = new ArrayList(); foreach (EntrantInfo en in ArrLst) { if (en.Num == num) { temp.Add(en); } } return temp; } } //還可以聲明多個版本的索引器... } class Program { static void Main(string[] args) { IndexerForEntrantInfo Info = new IndexerForEntrantInfo(); //this[string name, int num]的使用 Info["張三", 101] = "人事部"; Info["李四", 102] = "行政部"; Console.WriteLine(Info["張三", 101]); Console.WriteLine(Info["李四", 102]); Console.WriteLine(); //this[int num]的使用 foreach (EntrantInfo en in Info[102]) { Console.WriteLine(en.Name); Console.WriteLine(en.Department); } } } }