C# 索引器 使用详解

发布于:2025-02-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

总目录


前言

在 C# 中,数组和集合可以通过索引直接访问元素,例如 arr[0]。但如果想让自定义的类或结构体也支持这种直观的访问方式,该怎么办?这时,索引器(Indexer) 就派上用场了!索引器是 C# 中一个强大但常被忽视的特性,它能为你的类型赋予类似数组的访问能力,同时隐藏底层数据结构的复杂性。本文将深入探讨索引器的语法、应用场景及高级技巧,并通过大量示例代码帮助你彻底掌握这一特性。


一、索引器是什么?

1. 基本概念

  • 索引器允许类或结构的实例就像数组一样进行索引。 无需显式指定类型或实例成员,即可设置或检索索引值。 索引器类似于属性,不同之处在于它们的访问器需要使用参数。
  • 索引器是一种特殊成员,允许类或结构体通过索引(如 obj[0])访问其内部数据。它的核心作用是为类型提供类似数组或字典的访问接口,从而简化代码逻辑,提升可读性。

2. 索引器与属性的区别

  • 属性(Property):通过名称访问,例如 obj.Name
  • 索引器(Indexer):通过索引(参数)访问,例如 obj[0]obj["key"]
    索引器支持多参数,而属性没有参数。

在这里插入图片描述

3. 索引器有什么用?

在C#开发中,我们经常需要操作包含集合数据的自定义类型。传统方法通过Get/Set方法访问元素不仅繁琐,还破坏了代码的简洁性。索引器(Indexer)正是为解决这个问题而生,它允许我们使用类似数组的语法([])来访问对象内部集合,极大提升了代码的可读性和易用性。

二、索引器的使用

1. 索引器的定义

索引器使用 this 关键字定义,语法如下:

public 返回类型 this[参数类型 参数]
{
    get { /* 获取逻辑 */ }
    set { /* 设置逻辑 */ }
}

2. 基本操作

示例 1:普通对象中的使用

    public class Person
    {
        private string _name, _pwd, _phone;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public string Pwd
        {
            get { return _pwd; }
            set { _pwd = value; }
        }

        public string Phone
        {
            get { return _phone; }
            set { _phone = value; }
        }

		//通过this关键字定义索引器,this代表的就是当前对象的实例,[]内就是索引器的参数列表
        public string this[int index]
        {
            get
            {
                if (index == 0) return _name;
                else if (index == 1) return _pwd;
                else if (index == 2) return _phone;
                else return null;
            }
            set
            {
                if (index == 0) _name = value;
                else if (index == 1) _pwd = value;
                else if (index == 2) _phone = value;
            }
        }
    }

        static void Main(string[] args)
        {
            //使用实例的属性赋值和取值
            Person p = new Person();
            p.Name = "AAA";
            p.Pwd = "111";
            p.Phone = "11111111111";
            Console.WriteLine($"属性取值:Name={p.Name} Pwd={p.Pwd} Phone={p.Phone}");

            //使用实例的索引器赋值和取值
            Person person = new Person();
            person[0] = "BBB";
            person[1] = "222";
            person[2] = "222222222";
            Console.WriteLine($"索引器取值:Name={person[0]} Pwd={person[1]} Phone={person[2]}");

            Console.ReadLine();
        }

上面的案例通过定义一个索引器,来索引类中的每一个属性,使得我们可以使用类似于 person[0]的方式访问类中每一个属性并进行赋值和取值。

示例 2:封装数组的索引器

public class StringArray
{
    private string[] _array = new string[10];

    public string this[int index]
    {
        get => _array[index];
        set => _array[index] = value;
    }
}

// 使用示例
var arr = new StringArray();
arr[0] = "Hello";  // 调用 set 访问器
Console.WriteLine(arr[0]);  // 输出 "Hello"

示例 3:多维索引器

索引器的参数可以是 intstring 或其他类型(支持任意类型的参数),甚至支持多参数(多维索引器)。

public class Grid
{
    private int[,] _grid = new int[10, 10];

    public int this[int row, int col]
    {
        get => _grid[row, col];
        set => _grid[row, col] = value;
    }
}

// 使用示例
var grid = new Grid();
grid[3, 4] = 100;  // 设置二维索引的值

3. 进阶操作

1) 读写控制

索引器可以仅暴露 getset,实现只读或只写:

public class ReadOnlyList
{
    private int[] _data = { 1, 2, 3 };

    // 只读索引器(省略 set)
    public int this[int index] => _data[index];
}

2) 重载索引器

一个类可以定义多个索引器,通过参数类型或数量区分:

public class MultiIndexer
{
    private List<string> _names = new List<string> { "Alice", "Bob" };

    // 通过 int 索引获取名字
    public string this[int index] => _names[index];

    // 通过名字查找索引
    public int this[string name] => _names.IndexOf(name);
}

// 使用示例
var indexer = new MultiIndexer();
Console.WriteLine(indexer[0]);      // 输出 "Alice"
Console.WriteLine(indexer["Bob"]);  // 输出 1

3) 动态逻辑处理

getset 中添加自定义逻辑(如数据验证):

public class SafeArray
{
    private int[] _array = new int[10];

    public int this[int index]
    {
        get
        {
            if (index < 0 || index >= _array.Length)
                throw new IndexOutOfRangeException();
            return _array[index];
        }
        set
        {
            if (index < 0 || index >= _array.Length)
                throw new IndexOutOfRangeException();
            _array[index] = value;
        }
    }
}

4) 接口中的索引器

索引器可以在接口中定义,由实现类具体实现:

public interface IArrayContainer
{
    int this[int index] { get; set; }
}

public class MyArray : IArrayContainer
{
    private int[] _array = new int[10];

    public int this[int index]
    {
        get => _array[index];
        set => _array[index] = value;
    }
}

四、索引器的应用场景

1. 封装集合类

自定义集合类时,索引器可提供类似原生数组的访问方式:

public class CustomList<T>
{
    private T[] _items = new T[10];
    private int _count = 0;

    public T this[int index]
    {
        get => _items[index];
        set => _items[index] = value;
    }

    public void Add(T item) => _items[_count++] = item;
}

在这里插入图片描述

2. 多维数据或复杂结构 访问

例如矩阵、网格或表格数据的封装。以下示例是封装温度数据,通过日期索引访问:

public class TemperatureData
{
    private float[] _temperatures = new float[365];

    public float this[DateTime date]
    {
        get => _temperatures[date.DayOfYear - 1];
        set => _temperatures[date.DayOfYear - 1] = value;
    }
}

// 使用示例
var tempData = new TemperatureData();
tempData[new DateTime(2023, 7, 1)] = 35.5f;

3. 替代冗余方法

简化类似 GetValue(int index)SetValue(int index, T value) 的方法调用。

// 没有索引器的写法
var value = list.GetValue(0);
list.SetValue(0, "New Value");

// 使用索引器的写法
var value = list[0];
list[0] = "New Value";

五、注意事项

  • 命名规则
    索引器只能命名为 this,不能自定义名称。

  • 静态索引器
    C# 不支持静态索引器(如 public static int this[int index])。

  • 性能优化
    避免在索引器中实现复杂逻辑(如数据库查询),尤其是在高频访问场景中。

  • 异常处理
    始终验证索引的有效性,避免抛出未处理的 IndexOutOfRangeException

  • 索引器与属性的区别

    • 属性通过名称访问(如 obj.Name),索引器通过索引访问(如 obj[0])。
    • 索引器可以有多个参数,属性没有参数。

合理使用索引器可以让代码更优雅,但需注意避免过度设计。如果你正在开发自定义集合或需要为类型提供直观的访问接口,索引器将是一个得力的工具!


结语

回到目录页:C#/.NET 知识汇总
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。


参考资料:
C# 官方文档 - 索引器