目录
C# 索引器(Indexer)
表达式主体定义
索引器概述
使用索引器
备注
示例 1
可靠编程
接口中的索引器
属性和索引器之间的比较
索引器是C#编程语言中的一个特性,它允许类或结构的实例像数组一样进行索引操作,而无需显式指定类型或实例成员。通过索引器,可以设置或检索特定索引位置的值。索引器类似于属性,但不同之处在于索引器的访问器需要使用参数。
下面是一个简单的示例,定义了一个泛型类,其中包含用于赋值和检索值的简单get和set访问器方法:
public class SampleCollection
{
private T[] arr = new T[100];
public T this[int i]
{
get { return arr[i]; }
set { arr[i] = value; }
}
}
在上面的示例中,定义了一个名为SampleCollection的泛型类,该类包含一个私有的T类型数组arr,并且定义了一个索引器this,用于对arr数组进行索引操作。
下面是一个使用索引器的示例程序:
class Program
{
static void Main()
{
var stringCollection = new SampleCollection();
stringCollection[0] = "Hello, ";
stringCollection[1] = "world!";
Console.Write(stringCollection[0] + stringCollection[1]);
}
}
在上面的示例程序中,创建了SampleCollection类的一个实例stringCollection,并使用索引器对其进行赋值和检索操作。最终输出"Hello, world!"。
当索引器的 get 或 set 访问器包含一个简单的返回或设置值的语句时,可以使用表达式主体定义来简化代码。自C# 6起,表达式主体成员提供了一种经过简化的语法,使得只读索引器的实现变得更加简洁。以下是一个示例:
public class SampleCollection
{
private T[] arr = new T[100];
public T this[int i] => arr[i];
}
在上面的示例中,我们定义了一个只读的索引器,使用了表达式主体定义。请注意,=> 引入了表达式主体,并未使用 get 关键字。这等价于下面的完整定义:
public class SampleCollection
{
private T[] arr = new T[100];
public T this[int i]
{
get { return arr[i]; }
}
}
通过使用表达式主体定义,可以将简单的只读索引器的实现代码变得更加简洁和易读。
自 C# 7.0 起,我们可以使用表达式主体定义来实现索引器的 get 和 set 访问器。对于只读索引器,我们可以使用简化的语法,不需要显式地使用 get 关键字。而对于读写索引器,需要同时使用 get 和 set 关键字。
下面是一个示例,展示了如何在 C# 7.0 及更新版本中使用表达式主体定义来实现只读和读写索引器:
public class SampleCollection
{
private T[] arr = new T[100];
// 只读索引器的表达式主体定义
public T this[int i] => arr[i];
// 读写索引器的表达式主体定义
public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
}
在这个示例中,我们展示了使用表达式主体定义来实现只读和读写索引器。对于只读索引器,我们可以省略显式的 get 关键字;而对于读写索引器,需要同时使用 get 和 set 关键字。
使用索引器可以用类似于数组的方式为对象建立索引。
get
取值函数返回值。 set
取值函数分配值。
this 关键字用于定义索引器。
value 关键字用于定义由 set
访问器分配的值。
索引器不必根据整数值进行索引;由你决定如何定义特定的查找机制。
索引器可被重载。
索引器可以有多个形参,例如当访问二维数组时。
索引器使你可从语法上方便地创建类、结构或接口,以便客户端应用程序可以像访问数组一样访问它们。 编译器将生成一个 Item 属性(或者如果存在 IndexerNameAttribute,也可以生成一个命名属性)和适当的访问器方法。 在主要目标是封装内部集合或数组的类型中,常常要实现索引器。 例如,假设有一个类 TempRecord,它表示 24 小时的周期内在 10 个不同时间点所记录的温度(单位为华氏度)。 此类包含一个 float[] 类型的数组 temps,用于存储温度值。 通过在此类中实现索引器,客户端可采用 float temp = tempRecord[4] 的形式(而非 float temp = tempRecord.temps[4])访问 TempRecord 实例中的温度。 索引器表示法不但简化了客户端应用程序的语法;还使类及其目标更容易直观地为其它开发者所理解。
若要在类或结构上声明索引器,请使用 this 关键字,如以下示例所示:
// 索引器声明
public int this[int index]
{
// 获取和设置访问器
}
注意:通过声明索引器,可自动在对象上生成一个名为 Item 的属性。 无法从实例成员访问表达式直接访问 Item 属性。 此外,如果通过索引器向对象添加自己的 Item 属性,则将收到 CS0102 编译器错误。 要避免此错误,请使用 IndexerNameAttribute 来重命名索引器,详细信息如下所示。
索引器及其参数的类型必须至少具有和索引器相同的可访问性。 有关可访问性级别的详细信息,请参阅访问修饰符。
有关如何在接口上使用索引器的详细信息,请参阅接口索引器。
索引器的签名由其形参的数目和类型所组成。 它不包含索引器类型或形参的名称。 如果要在相同类中声明多个索引器,则它们的签名必须不同。
索引器未分类为变量;因此,索引器值不能按引用(作为 ref 或 out 参数)传递,除非其值是引用(即按引用返回。)
若要使索引器的名称可为其他语言所用,请使用 System.Runtime.CompilerServices.IndexerNameAttribute,如以下示例所示:
// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
// get and set accessors
}
此索引器被索引器名称属性重写,因此其名称将为 TheItem。 默认情况下,默认名称为 Item。
using System;
public class SampleCollection
{
private T[] arr = new T[100];
// 定义索引器
public T this[int i]
{
get { return arr[i]; }
set { arr[i] = value; }
}
}
public class Program
{
public static void Main()
{
// 创建SampleCollection实例
SampleCollection collection = new SampleCollection();
// 设置索引器的值
collection[0] = "Hello";
collection[1] = "World";
// 获取索引器的值
string firstElement = collection[0];
string secondElement = collection[1];
// 输出结果
Console.WriteLine(firstElement); // 输出: Hello
Console.WriteLine(secondElement); // 输出: World
}
}
在上述示例中,我们定义了一个名为 SampleCollection 的泛型类,它具有一个内部数组 arr,长度为100。然后,我们通过定义索引器使得可以像访问数组一样访问 SampleCollection 实例的元素。
在 Main 方法中,我们创建了 SampleCollection
最后,我们将 firstElement 和 secondElement 的值输出到控制台,验证索引器的使用结果。
提高索引器的安全性和可靠性有两种主要方法:
请确保结合某一类型的错误处理策略,以处理万一客户端代码传入无效索引值的情况。 在本主题前面的第一个示例中,TempRecord 类提供了 Length 属性,使客户端代码能在将输入传递给索引器之前对其进行验证。 也可将错误处理代码放入索引器自身内部。 请确保为用户记录在索引器的访问器中引发的任何异常。
在可接受的程度内,为 get 和 set 访问器的可访问性设置尽可能多的限制。 这一点对 set 访问器尤为重要。 有关详细信息,请参阅限制访问器可访问性。
在C#中,接口是抽象类的一种形式,它定义了一个类或结构体应遵循的成员列表。然而,在接口中并不能直接包含字段,因此无法在接口中定义索引器。索引器必须与具体的实现相关联,而接口只能定义方法、属性、事件和索引器的成员契约,而不包含实际的实现。
换句话说,接口可以定义一个索引器的契约,但具体的实现需要在实现接口的类中完成。下面是一个示例:
using System;
public interface IIndexable
{
// 索引器的契约
string this[int index] { get; set; }
}
public class MyCollection : IIndexable
{
private string[] _data = new string[100];
// 实现接口中定义的索引器
public string this[int index]
{
get { return _data[index]; }
set { _data[index] = value; }
}
}
public class Program
{
public static void Main()
{
MyCollection collection = new MyCollection();
collection[0] = "Hello";
collection[1] = "World";
Console.WriteLine(collection[0]); // 输出: Hello
Console.WriteLine(collection[1]); // 输出: World
}
}
在上述示例中,我们定义了一个名为 IIndexable 的接口,其中包含了一个索引器的契约。然后,在 MyCollection 类中,我们实现了 IIndexable 接口,并提供了对应的索引器实现。
通过这种方式,我们可以在接口中定义索引器的契约,然后在实现接口的类中完成对索引器的具体实现。这样做的好处是,当多个类需要实现相同的索引器行为时,可以通过接口来统一规范,从而提高代码的可维护性和扩展性。
索引器与属性在许多方面相似,但确实存在一些重要的差异。以下是这两者之间的主要区别:
访问方式:属性通过使用简单的名称来访问,而索引器使用索引来访问。属性可以像访问字段一样进行访问,而索引器使用类似于数组的语法来访问集合中的元素。
成员类型:属性可以是静态成员或实例成员,而索引器只能是实例成员。这意味着索引器只能通过实例对象进行访问。
参数:属性的get访问器没有参数,而索引器的get访问器具有与索引器相同的形参列表,以便接受索引参数。同样,属性的 set 访问器具有一个隐式的 value 参数,而索引器的 set 访问器也具有与索引器相同的形参列表,包括 value 参数。
简洁语法:对于属性,可以使用 自动实现的属性 来提供简洁的语法,而索引器只支持expression-bodied成员的简洁语法。
综上所述,尽管属性和索引器有许多相似之处,但它们在访问方式、成员类型、参数和语法等方面存在一些重要的差异。