1.什么是索引器
属性可被视为一种智能字段:类似地,索引器可被视为一种智能数组(索引器本质上是“有参属性”:而上一片博客所说的普通属性是“无参属性”。 “索引器”只是C#对“有参属性”的叫法。)。属性封装了类中的一个值,而索引器封装了一组值。使用索引器时,语法和使用数组完全相同。
1.1不用索引器的例子
例如:以下表达式使用左位移(<<)和按位AND(&)操作符判断在名为bits的一个int中,位于位置5(右数第6位)的二进制是0还是1:
(bits & (1<<5))!= 0
如果bits变量包含十进制值42,即二进制00101010。 十进制值1的二进制是0000001.所以表达式1 << 5的结果是00100000。右数第6位是1。因此,表达式bits& (1 << 5)相当于00101010 & 00100000.结果是00100000(非零)。 如果bits变量包含65,或者01000001,那么表达式01000001 &01000000结果是00000000,A即十进制0。
虽然这已经是一个比较复杂的表达式,但和下而这个表达式(使用复合赋值操作符&=将位置6的位设为0)相比,其复杂性又显得微不足道了:
bits &= ~(1 << 5)
类似地,要将位置6的位设为1,可用按位OR(|)操作符。下面这个复杂的表达式以复合赋值操作符|=为基础:
bits |= (1<< 5)
这些例子的通病在于,虽然能起作用,但不能清楚表示为什么要这样写,我们搞不清楚它们是如何工作的。过于复杂,解决方案很低级。也就是说,无法对要解决的问题进行
抽象,会造成难以维护的代码。
1.2同一个例子改用索引器
现在,暂停对前面的低级解决方案的思索,将重点放在问题的本质上。现在需要的是将int作为一个由32个二进制位构成的数组使用,而不是作为int使用。所以,解决问题的最佳方案是将int想象成包含32位的一个数组!也就是说,如果bits是int,那么为了访问右数第6个二进制位,我们想这样写(记住索引从0开始):
bits[5]
为了将右数第4位设为true,我们希望能像下面这样写:
bits[3] = true;
遗憾的是,不能为int使用方括号记号法。这种记号法仅适合数组或行为与数组相似的类型。所以,解决方案是新建一.种类型,它在行为、外观和用法上都类似于bool数组,但它用int实现。需为此定义一个索引器。假定新类型名为IntBits. IntBits 将包含一个int值(在构造器中初始化),但我们要将IntBits作为由bool变量构成的数组使用:
struct IntBits
{
private int bits;
publlc IntBits(int initialBitValue)
{
bits.initialBitValue;
}
//在这里写索引器
}
提示:由于IntBits很小,是轻量级的,所以有必要把它作为结构而不是类来创建。
定义索引器要采取一种 兼具属性和数组特征的记号法。索引器由this 关键字引入,this之前指定索引器的返回值类型。在this之后的方括号中,指定作为索引器的索引使用的值的类型。IntBits结构的索引器使用整数作为索引类型,返回bool值,如下所示:
struct IntBits
{
public bool this [ int index ]
{
get
{
return (bits & (1 << index)) != 0;
}
set{
if (value) //如果value为true,就将指定的位设为1(开):否则设为B(关)
bits |= (1 << index);
else
bits &= ~(1 << index);
}
}
}
注意一下几点:
(1)索引器不是方法——没有一对包含参数的圆括号,但有一对指定了索引的方括号。索引指定要访问哪一个元素。
(2)所有索引都使用this关键字取代方法名。每个类或结构只允许定义一个索引器(虽然可以重载并有多个实现),而且总是命名为this。
(3)和属性一样,索引器也包含get和set这两个访问器。本例的get和set访问器包含前面讨论过的按位表达式。
(4)索引器声明中指定的index将调用索引器时指定的索引值来填充。get和set访问器方法可以读取这个实参,判断应访问哪一个元素。
声明好索引器后,就可用IntBits(而非int)类型的变量并使用方括号记号法:
int adapted = 126; // 126的二进制形式是01111110
IntBits bits = new IntBits(adapted);
bool peek = bits[6]; //获取索引位置6的bool值:应该是true(1)
bits[0] = true; //将家引0的位设为true(1)
bits[3] = false;//将索引3的位设为false(0)
//现在bits的值是01110111或十进制119
1.3理解索引器的访问器
读取索引器时,编译器自动将数组风格的代码转换成对那个索引器的get访问器的调用。例如,以下代码转换成对bits的get访问器的调用,index 参数值设为6;
bool peek = bits[6];
类似地,向索引器写入时,编译器将数组风格的代码转换成对索引器的set访问器的调用,并将index参数设为方括号中指定的值。例如:
bits[3] = true;
该语句将转换成对bits的set访问器的调用,index 值设为3.和普通属性一一样,向索引器写入的值(本例是true)是通过value关键字来访问的。value 的类型与索引器本身的类型相同(本例是bool)。
还可在同时读取和写入的情况下使用索引器。这种情况要同时用到get和set访问器。例如,以下语句使用XOR操作符(^)反转bits变量索引6的二进制位:
bits[6] = true;
它自动转换成以下形式:
bits[6] = bits[6] ^ true;
上述代码之所以能奏效,是由于索引器同时声明了get 和set访问器。
1.4对比索引器和数组
(1)索引器能使用非数值下标:
public int this [string name]{…}//合法
(2)索引 器能重载(这和方法相似),数组则不能:
public Name this [ PhoneNumber number ] { ... }
public PhoneNumber this [ Name name ] { ... }
(3)索引器不能作为 ref或out参数使用,数组元素则能:
IntBits bits; // bits包含一个索引器
Method(ref bits[1]); // 编译时错误
2.接口中的索引器
可在接口中声明索引器。为此,需要指定get以及/或者set关键字。但是,get和set访问器的主体要替换成分号。实现该接口的任何类或结构都必须实现接口所声明的索引器的访问器,例如:
interface TRawInt
{
bool this [ int index 1 { get; set; }
}
struct RawInt : IRawInt
{
…
public bool this [ int index ]
{
get{...}
set{...}
}
…
}
在类中实现接口要求的索引器时,可将索引器的实现声明为virtual,从而允许派生类重写get和set访问器。例如,前面的例子可以改写成以下形式:
class RawInt : IRawInt
{
…
public virtual bool this [ int index ]
{
get {...}
set {... }
}
…
}
还可以附加接口名称作为前缀,通过“显式接口实现”语法(参见之前的博客)来实现索引器。索引器的显式实现是非公共和非虚的(所以不能被重写),例如:
struct RawInt : TRawInt
{
…
bool IRawInt.this [ int index ]
{
get{...}
set{...}
}
…
}
参考书籍:《Visual C#从入门到精通》