使用索引器(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 来重命名索引器,详细信息如下所示。

备注

索引器及其参数的类型必须至少具有和索引器相同的可访问性。 有关可访问性级别的详细信息,请参阅访问修饰符

有关如何在接口上使用索引器的详细信息,请参阅接口索引器

索引器的签名由其形参的数目和类型所组成。 它不包含索引器类型或形参的名称。 如果要在相同类中声明多个索引器,则它们的签名必须不同。

索引器未分类为变量;因此,索引器值不能按引用(作为 refout 参数)传递,除非其值是引用(即按引用返回。)

若要使索引器的名称可为其他语言所用,请使用 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 属性,使客户端代码能在将输入传递给索引器之前对其进行验证。 也可将错误处理代码放入索引器自身内部。 请确保为用户记录在索引器的访问器中引发的任何异常。

  • 在可接受的程度内,为 getset 访问器的可访问性设置尽可能多的限制。 这一点对 set 访问器尤为重要。 有关详细信息,请参阅限制访问器可访问性

另请参阅