>> >> >> Reference << << << <<<<<<Ref>>>>>>
Indexer
Modified: 2025-06-01 | Author:ljf12825

在C#中,索引器(Indexer)允许对象像数组一样用下标[]访问内部数据
它提供一种更直观、更简洁的方式来访问类或结构体内部封装的数据元素,尤其是包含集合或数组的类
它本质上是类结构体定义的一个特殊属性(property),通过下标访问成员

核心思想:让对象具备“array-like”的行为

基本语法

索引器的声明类似于属性,但它使用this关键字,并且在方括号中指定参数

access_modifier return_type this[parameter_type parameter, ...]
{
    get 
    { 
        // 返回与提供参数对应的值
    }
    set
    {
        // 设置于提供参数对应的值
    }
}

示例

封装一个数组

public class DataBox
{
    private int[] _data = new int[10]; // 内部私有数组

    // 索引器:使用 int 作为索引
    public int this[int index]
    {
        get
        {
            // 可以进行边界检查,这是封装的一大优势
            if (index < 0 || index >= _data.Length)
                throw new IndexOutOfRangeException("Index is out of range");
            
            return _data[index];
        }
        set
        {
            if (index < 0 || index >= _data.Length)
                throw new IndexOutOfRangeException("Index is out of range");
            
            _data[index] = value;
        }
    }

    // 一个返回内部数组长度的方法
    public int Length => _data.Length;
}

class Program
{
    static void Main(string[] args)
    {
        DataBox box = new DataBox();

        // 使用索引器设置值 (调用 set 访问器)
        box[0] = 42;
        box[1] = 100;
        box[2] = 15;

        // 使用索引器获取值 (调用 get 访问器)
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine($"box[{i}] = {box[i]}");
        }

        // 输出:
        // box[0] = 42
        // box[1] = 100
        // box[2] = 15
    }
}

优势:在getset访问器中添加了边界检查,这是直接暴露公共数组无法轻易做到的

特性

  1. 索引器的参数类型不限于整数 示例:以字符串作为索引,模拟字典的行为
public class StringKeyCollection
{
    private Dictionary<string, string> _dictionary = new Dictionary<string, string>();

    // 索引器:使用 string 作为键
    public string this[string key]
    {
        get
        {
            // 如果键不存在,返回一个默认值,而不是抛出异常
            _dictionary.TryGetValue(key, out string value);
            return value;
        }
        set
        {
            _dictionary[key] = value;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        StringKeyCollection collection = new StringKeyCollection();

        collection["name"] = "Alice";
        collection["name"] = "C#";

        Console.WriteLine(collection["name"]); // Alice
        Console.WriteLine(collection["language"]); // C# 
        Console.WriteLine(collection["age"]); // 空字符串
    }
}
  1. 多维索引器 通过提供多个参数,可以实现多维索引器
public class Matrix
{
    private int[,] _matrix = new int[3, 3];

    // 多维索引器
    public int this[int row, int col]
    {
        get {  return _matrix[row, col]; } 
        set { _matrix[row, col] = value; }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var matrix = new Matrix();

        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                matrix[i, j] = i + j;
                Console.WriteLine(matrix[i, j]);
            }
        }
    }
}
  1. 索引器重载 和普通方法一样,索引器也可以被重载。只要参数列表(参数的类型或数量)不同即可
public class OverloadedIndexer
{
    private int[] _intData = new int[10];
    private string[] _stringData = new string[10];

    // 通过 int 索引访问 int 数据
    public int this[int index]
    {
        get => _intData[index];
        set => _intData[index] = value;
    }

    // 通过 int 索引访问 string 数据 (重载)
    public string this[int index]
    {
        get => _stringData[index];
        set => _stringData[index] = value;
    }

    // 通过 string 索引访问 string 数据 (另一个重载)
    public string this[string name]
    {
        get { /* ... */ return ""; }
        set { /* ... */ }
    }
}

常见用途

示例:索引器与LINQ结合

public class LinqIndexer
{
    private List<Person> people = new List<Person>();

    // 通过ID索引
    public Person this[int id] => people.FirstOrDefault(p => p.Id = id);

    // 通过姓名索引
    public IEnumerable<Person> this[string name] => people.Where(p => p.Name = name);

    // 通过条件索引
    public IEnumerable<Person> this[Func<Person, bool> predicate] => people.Where(predicate);

    public void Add(Person person) => people.Add(person);
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age( get; set; )
}

// 使用
var collection = new LinqIndexer();
collection.Add(new Person { Id = 1, Name = "Alice", Age = 25});
collection.Add(new Person { Id = 2, Name = "Bob", Age = 30});

var person = collection[1]; // 通过ID获取
var adults = collection[p => p.Age >= 18]; // 通过条件索引获取

底层与性能

// Source Code
public class MyCollection
{
    private string[] _data = new stirng[10];

    public string this[int index]
    {
        get { return _data[index]; }
        set { _data[index] = value; }
    }
}

编译后相当于

public class MyCollection
{
    private string[] _data = new string[10];

    // get 访问器变成 get_Item方法
    public string get_Item(int index)
    {
        return _data[index];
    }

    // set 访问器变成 set_Item方法
    public void set_Item(int index, string value)
    {
        _data[index] = value;
    }
}

IL代码层面

// get 访问器
.method public hidebysig specialname instance string get_Item(int32 index) cil managed
// set 访问器  
.method public hidebysig specialname instance void set_Item(int32 index, string value) cil managed

性能考虑
由于索引器在编译时就被转换成普通方法调用,所以没有运行时性能开销

最佳实践

  1. 保持简单:索引器应该简单高效,避免复杂逻辑
  2. 适当的错误处理:对无效索引提供合理的处理方式
  3. 文档化:清楚地文档化索引的行为和异常
  4. 一致性:保持索引器行为与类似集合的一致性
  5. 性能考虑:对于大型集合,考虑使用更高效的数据结构