1索引器是什么
索引器(Indexer)让一个对象像数组一样,用方括号加下标来访问。教程说,给一个类定义了索引器,它就像一个虚拟数组。
比如 names[0] = "Zara"; 里的 names 不是数组,而是 IndexedNames 类的对象,方括号写在对象后面。
视频里把 IndexedNames 比成一个名册柜:十个抽屉是私有数组 namelist,下标窗口就是索引器。真实的索引器没有柜子,它只是一对 get 和 set 方法。
2语法
| 13 | public string this[int index] | // string 是元素类型;名字的位置写 this,方括号里是参数 |
| 14 | { | // 索引器开始:里面放 get 和 set |
开头的元素类型就是取出来的值是什么类型;get 按 index 返回一个值,set 按 index 设置一个值。这个写法和上一节的属性很像,也是 get 和 set。不同的是:属性有自己的名字(比如 Name),读写一个特定的数据成员;索引器没有名字,用 this 关键字,this 指的就是这个对象本身,它把对象里的数据分成一小块一小块,用下标挑出其中一块来读写。
3一维索引器
| 6 | private string[] namelist = new string[size]; | // 私有字符串数组 namelist,长度是 size |
| 7 | static public int size = 10; | // size = 10;static 表示属于类本身,不属于某个对象 |
| 8 | public IndexedNames() | // 构造函数 |
| 9 | { | // 构造函数开始 |
| 10 | for (int i = 0; i < size; i++) | // 循环十次 |
| 11 | namelist[i] = "N. A."; | // 每一格都填上 "N. A.",意思是还没有名字 |
get 先检查下标在不在 0 到 9 之间,在就取出那一格,不在就返回空字符串,不会因为越界出错:
| 19 | if( index >= 0 && index <= size-1 ) | // 下标在 0 到 9 之间吗? |
| 20 | { | // if 开始 |
| 21 | tmp = namelist[index]; | // 在范围内:取出那一格的名字 |
| 22 | } | // if 结束 |
| 23 | else | // 否则(越界) |
| 24 | { | // else 开始 |
| 25 | tmp = ""; | // tmp 设为空字符串 |
| 26 | } | // else 结束 |
set 也先检查范围,下标越界时什么也不做:
| 32 | if( index >= 0 && index <= size-1 ) | // 同样先检查范围 |
| 33 | { | // if 开始 |
| 34 | namelist[index] = value; | // value 是等号右边赋进来的值,存进那一格 |
| 35 | } | // if 结束 |
Main 里先给 0 到 6 号格存进七个名字,再循环打印十格:
| 41 | IndexedNames names = new IndexedNames(); | // 创建对象:10 格全是 N. A. |
| 42 | names[0] = "Zara"; | // 方括号在等号左边,调用 set:0 号格换成 Zara |
| 43 | names[1] = "Riz"; | // 1 号格存 Riz(后面 2 到 6 号依次存名字) |
| 49 | for ( int i = 0; i < IndexedNames.size; i++ ) | // 循环十次,i 从 0 到 9 |
| 50 | { | // for 开始 |
| 51 | Console.WriteLine(names[i]); | // 读 names[i],调用的是 get |
| 52 | } | // for 结束 |
查看完整代码(56 行)
| 1 | using System; | // 引入 System 命名空间 |
| 2 | namespace IndexerApplication | // 定义命名空间 IndexerApplication |
| 3 | { | // 命名空间开始 |
| 4 | class IndexedNames | // 定义 IndexedNames 类 |
| 5 | { | // IndexedNames 类开始 |
| 6 | private string[] namelist = new string[size]; | // 私有数组 namelist,长度 size |
| 7 | static public int size = 10; | // 静态字段 size = 10 |
| 8 | public IndexedNames() | // 构造函数 |
| 9 | { | // 构造函数开始 |
| 10 | for (int i = 0; i < size; i++) | // 循环十次 |
| 11 | namelist[i] = "N. A."; | // 每格填 "N. A."(for 后面只有一句,可以不加大括号) |
| 12 | } | // 构造函数结束 |
| 13 | public string this[int index] | // 索引器:参数 int index,返回 string |
| 14 | { | // 索引器开始 |
| 15 | get | // get 访问器 |
| 16 | { | // get 开始 |
| 17 | string tmp; | // 临时变量 tmp |
| 18 | | |
| 19 | if( index >= 0 && index <= size-1 ) | // 下标在 0 到 9 之间? |
| 20 | { | // if 开始 |
| 21 | tmp = namelist[index]; | // 取出那一格 |
| 22 | } | // if 结束 |
| 23 | else | // 否则 |
| 24 | { | // else 开始 |
| 25 | tmp = ""; | // tmp 设为空字符串 |
| 26 | } | // else 结束 |
| 27 | | |
| 28 | return ( tmp ); | // 返回 tmp |
| 29 | } | // get 结束 |
| 30 | set | // set 访问器 |
| 31 | { | // set 开始 |
| 32 | if( index >= 0 && index <= size-1 ) | // 下标在范围内? |
| 33 | { | // if 开始 |
| 34 | namelist[index] = value; | // 存进那一格 |
| 35 | } | // if 结束 |
| 36 | } | // set 结束 |
| 37 | } | // 索引器结束 |
| 38 | | |
| 39 | static void Main(string[] args) | // 程序入口 Main 方法 |
| 40 | { | // Main 开始 |
| 41 | IndexedNames names = new IndexedNames(); | // 创建对象 names |
| 42 | names[0] = "Zara"; | // 调用 set:0 号格存 Zara |
| 43 | names[1] = "Riz"; | // 1 号格存 Riz |
| 44 | names[2] = "Nuha"; | // 2 号格存 Nuha |
| 45 | names[3] = "Asif"; | // 3 号格存 Asif |
| 46 | names[4] = "Davinder"; | // 4 号格存 Davinder |
| 47 | names[5] = "Sunil"; | // 5 号格存 Sunil |
| 48 | names[6] = "Rubic"; | // 6 号格存 Rubic |
| 49 | for ( int i = 0; i < IndexedNames.size; i++ ) | // 循环 0 到 9 |
| 50 | { | // for 开始 |
| 51 | Console.WriteLine(names[i]); | // 调用 get,打印第 i 格 |
| 52 | } | // for 结束 |
| 53 | Console.ReadKey(); | // 等待按键(现在的 VS 不写也会停住) |
| 54 | } | // Main 结束 |
| 55 | } | // IndexedNames 类结束 |
| 56 | } | // 命名空间结束 |
Zara Riz Nuha Asif Davinder Sunil Rubic N. A. N. A. N. A.
最后三行是 N. A.:7 到 9 号格子没人赋值,还是构造函数填进去的。
4它不是真数组
把循环条件改成 names.,编译报错:IndexedNames 里没有 Length。所以教程的循环写的是 IndexedNames.。
| 49 | for ( int i = 0; i < names.Length; i++ ) | // 对象没有数组的 Length |
Program.cs(49,37): error CS1061: “IndexedNames”未包含“Length”的定义,并且找不到可接受第一个“IndexedNames”类型参数的可访问扩展方法“Length”(是否缺少 using 指令或程序集引用?)
5重载索引器
索引器可以重载。第二个实例又加了一个 this[string name],返回 int,而且只有 get:
| 44 | int index = 0; | // 从 0 号格开始 |
| 45 | while(index < size) | // 还没比对完十格就继续 |
| 46 | { | // while 开始 |
| 47 | if (namelist[index] == name) | // 这一格的名字和要找的一样吗? |
| 48 | { | // if 开始 |
| 49 | return index; | // 一样就返回这一格的下标 |
| 50 | } | // if 结束 |
| 51 | index++; | // 下标加 1,比对下一格 |
| 52 | } | // while 结束 |
| 53 | return index; | // 找遍了都没有:此时 index 是 10 |
| 73 | // 使用带有 string 参数的第二个索引器 | |
| 74 | Console.WriteLine(names["Nuha"]); | // 方括号里是字符串,用第二个索引器:Nuha 在 2 号格 |
两个索引器靠参数类型区分:方括号里放整数用第一个,放字符串用第二个。方括号里的参数不一定是整数。运行后前十行和第一个实例一样,接着输出 2(完整输出见下一小节的前 11 行)。
6查不到和越界
| 75 | Console.WriteLine(names["Bob"]); | // 查一个没有的名字:返回 10,不是有效下标 |
| 76 | Console.WriteLine("[" + names[12] + "]"); | // 读越界的下标 12:get 走到 else,返回空字符串 |
查看完整代码(80 行)
| 1 | using System; | // 引入 System 命名空间 |
| 2 | namespace IndexerApplication | // 定义命名空间 IndexerApplication |
| 3 | { | // 命名空间开始 |
| 4 | class IndexedNames | // 定义 IndexedNames 类 |
| 5 | { | // IndexedNames 类开始 |
| 6 | private string[] namelist = new string[size]; | // 私有数组 namelist |
| 7 | static public int size = 10; | // 静态字段 size = 10 |
| 8 | public IndexedNames() | // 构造函数 |
| 9 | { | // 构造函数开始 |
| 10 | for (int i = 0; i < size; i++) | // 循环十次 |
| 11 | { | // for 开始 |
| 12 | namelist[i] = "N. A."; | // 每格填 "N. A." |
| 13 | } | // for 结束 |
| 14 | } | // 构造函数结束 |
| 15 | public string this[int index] | // 第一个索引器:参数 int |
| 16 | { | // 索引器开始 |
| 17 | get | // get 访问器 |
| 18 | { | // get 开始 |
| 19 | string tmp; | // 临时变量 tmp |
| 20 | | |
| 21 | if( index >= 0 && index <= size-1 ) | // 下标在范围内? |
| 22 | { | // if 开始 |
| 23 | tmp = namelist[index]; | // 取出那一格 |
| 24 | } | // if 结束 |
| 25 | else | // 否则 |
| 26 | { | // else 开始 |
| 27 | tmp = ""; | // tmp 设为空字符串 |
| 28 | } | // else 结束 |
| 29 | | |
| 30 | return ( tmp ); | // 返回 tmp |
| 31 | } | // get 结束 |
| 32 | set | // set 访问器 |
| 33 | { | // set 开始 |
| 34 | if( index >= 0 && index <= size-1 ) | // 下标在范围内? |
| 35 | { | // if 开始 |
| 36 | namelist[index] = value; | // 存进那一格 |
| 37 | } | // if 结束 |
| 38 | } | // set 结束 |
| 39 | } | // 第一个索引器结束 |
| 40 | public int this[string name] | // 第二个索引器:参数 string,返回下标 |
| 41 | { | // 索引器开始 |
| 42 | get | // 只有 get |
| 43 | { | // get 开始 |
| 44 | int index = 0; | // 从 0 号格开始 |
| 45 | while(index < size) | // 比对十格 |
| 46 | { | // while 开始 |
| 47 | if (namelist[index] == name) | // 名字一样? |
| 48 | { | // if 开始 |
| 49 | return index; | // 返回这一格的下标 |
| 50 | } | // if 结束 |
| 51 | index++; | // 比对下一格 |
| 52 | } | // while 结束 |
| 53 | return index; | // 没找到:返回 10 |
| 54 | } | // get 结束 |
| 55 | | |
| 56 | } | // 第二个索引器结束 |
| 57 | | |
| 58 | static void Main(string[] args) | // 程序入口 Main 方法 |
| 59 | { | // Main 开始 |
| 60 | IndexedNames names = new IndexedNames(); | // 创建对象 names |
| 61 | names[0] = "Zara"; | // 0 号格存 Zara |
| 62 | names[1] = "Riz"; | // 1 号格存 Riz |
| 63 | names[2] = "Nuha"; | // 2 号格存 Nuha |
| 64 | names[3] = "Asif"; | // 3 号格存 Asif |
| 65 | names[4] = "Davinder"; | // 4 号格存 Davinder |
| 66 | names[5] = "Sunil"; | // 5 号格存 Sunil |
| 67 | names[6] = "Rubic"; | // 6 号格存 Rubic |
| 68 | // 使用带有 int 参数的第一个索引器 | |
| 69 | for (int i = 0; i < IndexedNames.size; i++) | // 循环 0 到 9 |
| 70 | { | // for 开始 |
| 71 | Console.WriteLine(names[i]); | // 打印第 i 格 |
| 72 | } | // for 结束 |
| 73 | // 使用带有 string 参数的第二个索引器 | |
| 74 | Console.WriteLine(names["Nuha"]); | // 查 Nuha:输出 2 |
| 75 | Console.WriteLine(names["Bob"]); | // 查 Bob:输出 10 |
| 76 | Console.WriteLine("[" + names[12] + "]"); | // 读 12 号格:输出 [] |
| 77 | Console.ReadKey(); | // 等待按键(现在的 VS 不写也会停住) |
| 78 | } | // Main 结束 |
| 79 | } | // IndexedNames 类结束 |
| 80 | } | // 命名空间结束 |
Zara Riz Nuha Asif Davinder Sunil Rubic N. A. N. A. N. A. 2 10 []
这个查名的索引器没有 set,给它赋值,编译报错:它是只读的。
| 74 | names["Nuha"] = 5; | // 给只有 get 的 this[string] 赋值 |
Program.cs(74,10): error CS0200: 无法为属性或索引器“IndexedNames.this[string]”赋值 - 它是只读的
7多个参数
索引器可以带多个参数,每个参数的类型也可以不同。教程没有给例子,我们自己写了一个座位表:排号 'B' - 'A' 得 1、座号 3 - 1 得 2,换算成数组的行和列(都从 0 数)。
| 6 | private string[,] seats = new string[3, 4]; | // 二维数组 seats:3 排,每排 4 个座位([,] 表示二维) |
| 7 | public string this[char row, int seat] | // 两个参数:char 排号、int 座号 |
| 8 | { | // 索引器开始 |
| 9 | get { return seats[row - 'A', seat - 1]; } | // get:换算成行和列,取出名字 |
| 10 | set { seats[row - 'A', seat - 1] = value; } | // set:按同样的换算存进名字 |
| 16 | chart['B', 3] = "Riz"; | // B 排 3 号存进 Riz |
| 17 | Console.WriteLine(chart['B', 3]); | // 读出来:输出 Riz |
查看完整代码(20 行)
| 1 | using System; | // 引入 System 命名空间 |
| 2 | namespace IndexerApplication | // 定义命名空间 IndexerApplication |
| 3 | { | // 命名空间开始 |
| 4 | class SeatChart | // 定义 SeatChart 类(座位表) |
| 5 | { | // SeatChart 类开始 |
| 6 | private string[,] seats = new string[3, 4]; | // 二维数组 seats:3 排,每排 4 个座位([,] 表示二维) |
| 7 | public string this[char row, int seat] | // 索引器有两个参数:char 类型的排号,int 类型的座号 |
| 8 | { | // 索引器开始 |
| 9 | get { return seats[row - 'A', seat - 1]; } | // get:'B' - 'A' = 1、3 - 1 = 2,取出名字 |
| 10 | set { seats[row - 'A', seat - 1] = value; } | // set:按同样的换算存进名字 |
| 11 | } | // 索引器结束 |
| 12 | | |
| 13 | static void Main(string[] args) | // 程序入口 Main 方法 |
| 14 | { | // Main 开始 |
| 15 | SeatChart chart = new SeatChart(); | // 创建座位表 |
| 16 | chart['B', 3] = "Riz"; | // B 排 3 号存进 Riz |
| 17 | Console.WriteLine(chart['B', 3]); | // 按同样的排号和座号读出来 |
| 18 | } | // Main 结束 |
| 19 | } | // SeatChart 类结束 |
| 20 | } | // 命名空间结束 |
Riz
8小结
- 索引器让对象像数组一样用下标访问,写法是
this加方括号参数。 - 它和属性一样有 get 和 set,但没有名字,读写的是对象里的一小块。
- 索引器可以重载,靠参数类型区分;只有 get 的只能读,不能赋值。
下一节:能不能把一个方法也装进变量里,像数据一样传来传去?
