使用索引器(C# 编程指南)
索引器使你可从语法上方便地创建类、结构或接口,以便客户端应用程序可以像访问数组一样访问它们。 编译器会生成一个 Item
属性(或者如果存在 IndexerNameAttribute,也可以生成一个命名属性)和适当的访问器方法。 在主要目标是封装内部集合或数组的类型中,常常要实现索引器。 例如,假设有一个类 TempRecord
,它表示 24 小时的周期内在 10 个不同时间点所记录的温度(单位为华氏度)。 此类包含一个 float[]
类型的数组 temps
,用于存储温度值。 通过在此类中实现索引器,客户端可采用 float temp = tempRecord[4]
的形式(而非 float temp = tempRecord.temps[4]
)访问 TempRecord
实例中的温度。 索引器表示法不但简化了客户端应用程序的语法;还使类及其目标更容易直观地为其它开发者所理解。
若要在类或结构上声明索引器,请使用 this 关键字,如以下示例所示:
// Indexer declaration
public int this[int index]
{
// get and set accessors
}
重要
通过声明索引器,可自动在对象上生成一个名为 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
。
示例 1
下列示例演示如何声明专用数组字段 temps
和索引器。 索引器可以实现对实例 tempRecord[i]
的直接访问。 若不使用索引器,则将数组声明为公共成员,并直接访问其成员 tempRecord.temps[i]
。
public class TempRecord
{
// Array of temperature values
float[] temps =
[
56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F
];
// To enable client code to validate input
// when accessing your indexer.
public int Length => temps.Length;
// Indexer declaration.
// If index is out of range, the temps array will throw the exception.
public float this[int index]
{
get => temps[index];
set => temps[index] = value;
}
}
请注意,当评估索引器访问时(例如在 Console.Write
语句中),将调用 get 访问器。 因此,如果不存在 get
访问器,则会发生编译时错误。
var tempRecord = new TempRecord();
// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;
// Use the indexer's get accessor
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}
使用其他值进行索引
C# 不将索引参数类型限制为整数。 例如,对索引器使用字符串可能有用。 通过搜索集合内的字符串并返回相应的值,可以实现此类索引器。 访问器可被重载,因此字符串和整数版本可以共存。
示例 2
下面的示例声明了存储星期几的类。 get
访问器采用字符串(星期几)并返回对应的整数。 例如,“Sunday”返回 0,“Monday”返回 1,依此类推。
// Using a string as an indexer value
class DayCollection
{
string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[string day] => FindDayIndex(day);
private int FindDayIndex(string day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
}
}
使用示例 2
var week = new DayCollection();
Console.WriteLine(week["Fri"]);
try
{
Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
示例 3
下面的示例声明了使用 System.DayOfWeek 存储星期几的类。 get
访问器采用 DayOfWeek
(表示星期几的值)并返回对应的整数。 例如,DayOfWeek.Sunday
返回 0,DayOfWeek.Monday
返回 1,依此类推。
using Day = System.DayOfWeek;
class DayOfWeekCollection
{
Day[] days =
[
Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
Day.Thursday, Day.Friday, Day.Saturday
];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[Day day] => FindDayIndex(day);
private int FindDayIndex(Day day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
}
}
使用示例 3
var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);
try
{
Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
可靠编程
提高索引器的安全性和可靠性有两种主要方法:
请确保结合某一类型的错误处理策略,以处理万一客户端代码传入无效索引值的情况。 在本文前面的第一个示例中,TempRecord 类提供了 Length 属性,使客户端代码能在将输入传递给索引器之前对其进行验证。 也可将错误处理代码放入索引器自身内部。 请确保为用户记录在索引器的访问器中引发的任何异常。
在可接受的程度内,为 get 和 set 访问器的可访问性设置尽可能多的限制。 这一点对
set
访问器尤为重要。 有关详细信息,请参阅限制访问器可访问性。