C# 索引器(Indexer)(超详细)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

在 C# 编程中,索引器(Indexer)是一个常被低估但功能强大的特性。它允许开发者像操作数组或集合一样,通过方括号 [] 的语法访问对象的成员。索引器的设计灵感来源于数组的下标访问方式,但其应用范围远不止于此。通过索引器,开发者可以灵活地控制对象内部数据的读写逻辑,甚至实现类似“动态属性”的功能。对于初学者而言,理解索引器的原理和用法,不仅能提升代码的简洁性,还能增强对面向对象编程(OOP)设计模式的掌握。本文将从基础语法、应用场景、高级特性到常见问题,系统性地解析 C# 索引器的全貌。


索引器的基本语法与核心概念

什么是索引器?

索引器是一种允许通过 对象[索引] 的语法访问对象成员的特殊属性。与普通属性不同,索引器不需要显式声明名称,而是通过 this 关键字定义。例如,当我们访问数组 arr[0] 时,实际上调用了数组类型的索引器。

基本语法结构

索引器的定义遵循以下格式:

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

关键点:

  1. 必须使用 this 关键字作为访问符;
  2. 参数列表可以包含多个类型(如 intstring 等),但必须至少有一个参数;
  3. getset 访问器可单独或同时存在。

与数组的对比

索引器的设计灵感来自数组,但其灵活性远超数组。例如,数组的索引只能是 int 类型,且必须连续;而索引器可以接受任意类型参数,甚至支持多维索引(如 obj[row, column])。


索引器的典型应用场景

场景一:模拟数组或集合的访问方式

假设我们需要创建一个自定义数据结构 Matrix,用于存储二维数据。通过索引器,可以像操作数组一样访问元素:

public class Matrix {  
    private int[,] _data;  

    public Matrix(int rows, int cols) {  
        _data = new int[rows, cols];  
    }  

    public int this[int row, int column] {  
        get => _data[row, column];  
        set => _data[row, column] = value;  
    }  
}  
// 使用示例:  
var matrix = new Matrix(3, 3);  
matrix[0, 1] = 10;  
Console.WriteLine(matrix[0, 1]); // 输出 10  

比喻:索引器就像一本书籍的目录,通过章节和页码(索引参数)快速定位内容(数据)。


场景二:数据验证与封装

索引器可以结合 getset 访问器,实现数据验证逻辑。例如,一个 AgeCollection 类,确保年龄值始终为正数:

public class AgeCollection {  
    private int[] _ages;  

    public AgeCollection(int size) {  
        _ages = new int[size];  
    }  

    public int this[int index] {  
        get => _ages[index];  
        set {  
            if (value < 0)  
                throw new ArgumentOutOfRangeException("年龄不能为负数");  
            _ages[index] = value;  
        }  
    }  
}  

此示例展示了索引器如何替代公共字段,同时提供输入校验,体现了封装原则。


场景三:动态属性访问

索引器可以接受 string 类型参数,实现类似字典(Dictionary)的键值对访问:

public class PropertyBag {  
    private Dictionary<string, object> _properties = new();  

    public object this[string key] {  
        get => _properties[key];  
        set => _properties[key] = value;  
    }  
}  
// 使用示例:  
var bag = new PropertyBag();  
bag["name"] = "Alice";  
Console.WriteLine(bag["name"]); // 输出 Alice  

对比:此场景与属性(Property)的区别在于,属性需要预先声明名称(如 Name),而索引器通过动态键名实现灵活访问。


索引器的高级特性与最佳实践

特性一:多索引器重载

一个类可以定义多个索引器,只要它们的参数列表不同。例如,一个 MultiIndexer 类支持 intstring 类型的索引:

public class MultiIndexer {  
    private Dictionary<string, object> _properties = new();  
    private object[] _items = new object[10];  

    // 整数索引器  
    public object this[int index] {  
        get => _items[index];  
        set => _items[index] = value;  
    }  

    // 字符串索引器  
    public object this[string key] {  
        get => _properties[key];  
        set => _properties[key] = value;  
    }  
}  

注意:索引器重载需通过参数类型区分,不能仅靠数量或顺序。


特性二:只读或只写索引器

与普通属性类似,索引器可以省略 getset 访问器,例如只读索引器:

public class ReadOnlyIndexer {  
    private string[] _data = { "A", "B", "C" };  

    public string this[int index] {  
        get => _data[index];  
    }  
}  
// 使用时无法赋值:  
var ro = new ReadOnlyIndexer();  
ro[0] = "X"; // 编译错误  

特性三:泛型索引器

结合泛型,索引器可以实现类型安全的访问:

public class GenericIndexer<T> {  
    private T[] _items;  

    public GenericIndexer(int size) {  
        _items = new T[size];  
    }  

    public T this[int index] {  
        get => _items[index];  
        set => _items[index] = value;  
    }  
}  
// 使用:  
var list = new GenericIndexer<string>(3);  
list[0] = "Hello";  
Console.WriteLine(list[0]); // 输出 Hello  

常见问题与解决方案

问题一:索引器与属性的区别

特性属性(Property)索引器(Indexer)
语法对象.属性名对象[索引参数]
数据访问固定名称,单一值动态参数,多个值
典型用途封装字段,提供逻辑控制动态访问集合或数据结构

解决方案:若需固定名称的访问,使用属性;若需通过参数动态访问数据,选择索引器。


问题二:索引器参数类型的选择

  • 推荐类型int(最常用)、string(键值对)、enum(枚举类型)
  • 避免类型:复杂对象或可变类型(如 List<T>),可能导致代码难以维护

问题三:性能优化

索引器内部应避免复杂计算或数据库操作。例如,以下代码因每次访问都计算平方根,可能影响性能:

public class SlowIndexer {  
    private double[] _data;  

    public double this[int index] {  
        get => Math.Sqrt(_data[index]); // 每次访问计算平方根  
    }  
}  

优化建议:将计算移到其他方法,或缓存结果。


综合案例:实现自定义集合类

以下示例演示如何通过索引器创建一个支持动态扩展的 DynamicArray 类:

public class DynamicArray {  
    private object[] _items = new object[0];  

    public int Count => _items.Length;  

    public object this[int index] {  
        get {  
            if (index < 0 || index >= Count)  
                throw new IndexOutOfRangeException();  
            return _items[index];  
        }  
        set {  
            if (index < 0)  
                throw new IndexOutOfRangeException();  
            if (index >= Count) {  
                Array.Resize(ref _items, index + 1);  
            }  
            _items[index] = value;  
        }  
    }  
}  
// 使用示例:  
var arr = new DynamicArray();  
arr[5] = "Hello"; // 自动扩展到索引5  
Console.WriteLine(arr.Count); // 输出 6  

此案例展示了索引器如何动态管理集合大小,并处理越界情况。


结论

C# 索引器(Indexer)通过简洁的语法和灵活的设计,为开发者提供了强大的数据访问能力。无论是模拟数组、实现数据验证,还是构建动态属性,索引器都能显著提升代码的可读性和可维护性。对于初学者,建议从基础语法开始,逐步尝试结合多索引器重载和泛型特性,最终掌握这一工具的精髓。记住,索引器的核心价值在于“以用户友好的方式暴露复杂逻辑”,而非单纯替代数组或集合。通过合理设计,索引器可以成为面向对象编程中不可或缺的利器。

关键词布局回顾:C# 索引器(Indexer)、索引器语法、索引器重载、泛型索引器、索引器性能优化
SEO策略:通过场景化描述和对比表格,自然嵌入关键词,满足搜索意图如“如何实现C#索引器”“索引器与属性区别”等。

最新发布