CodeGym /课程 /C# SELF /C#高级索引器:参数、修饰符、现代特性

C#高级索引器:参数、修饰符、现代特性

C# SELF
第 18 级 , 课程 1
可用

1. 多参数索引器

简单的索引器(比如this[int index])很适合你想用整数下标访问元素的时候——就像数组一样。但C#其实能做更多:你可以用多个参数、不同类型的参数、给getset设置不同的访问修饰符,还能在一个类里实现多个索引器(只要签名不一样)。

而且新版C#还加了不少新特性,让用索引器更爽,比如语法糖让代码更简洁。

索引器其实不一定只能接收一个int类型的下标。参数你自己定——要几个、什么类型都行,只要你的需求合理。


public class ChessBoard
{
    private string[,] board = new string[8, 8];

    // 索引器有两个参数!
    public string this[int row, int col]
    {
        get { return board[row, col]; }
        set { board[row, col] = value; }
    }
}
用于二维结构的双参数索引器

现在你可以这样写代码了:

ChessBoard chess = new ChessBoard();
chess[0, 0] = "车";
chess[7, 7] = "国王";
string piece = chess[0, 0]; // "车"

这种方式特别适合矩阵、二维地图、桌游、复杂集合啥的。

2. 不同类型参数的索引器

你的索引器不仅能接收int,还可以用任何合适的类型。只要对你的需求有意义就行。


using System.Collections.Generic;

public class Employee
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Position { get; set; }
}

public class EmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    // 用员工名字(string)做索引
    public Employee this[string name]
    {
        get
        {
            foreach (var employee in employees)
            {
                if (employee.Name == name)
                    return employee;
            }
            return null; // 或者你可以抛异常
        }
        set
        {
            for (int i = 0; i < employees.Count; i++)
            {
                if (employees[i].Name == name)
                {
                    employees[i] = value;
                    return;
                }
            }
            // 如果没有这个名字的员工——就加一个新的
            employees.Add(value);
        }
    }

    // 兼容性——用数字下标的索引器
    public Employee this[int index]
    {
        get { return employees[index]; }
        set { employees[index] = value; }
    }
}
var company = new EmployeeCollection();
company[0] = new Employee { Name = "伊万", Age = 30, Position = "程序员" };
company[1] = new Employee { Name = "玛丽亚", Age = 25, Position = "设计师" };

Employee employee = company["伊万"];
company["彼得"] = new Employee { Name = "彼得", Age = 28, Position = "测试员" };

注意:如果你有多个索引器——它们的参数类型和数量必须不一样。

3. get和set用不同访问修饰符

有时候你只想让别人读不能写(或者反过来)。在C#里你可以给getset分别设置不同的访问修饰符。


public class SecureEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get { return employees[index]; }
        internal set { employees[index] = value; }
    }
}

这种写法经常用来保护集合不被随便改,让类更可控。

4. 只读和只写索引器

有时候你只想让人读,或者只让人写索引器。


public class ReadOnlyEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get { return employees[index]; }
        // 没有set——不能改!
    }
}

public class WriteOnlyEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        set
        {
            employees.Insert(index, value);
        }
        // 没有get——不能读!
    }
}

实际项目里“只写”几乎用不到:一般都是“只读”,比如只让外部看不能改数据。

5. 带边界检查和逻辑的索引器

做得好的例子不仅要能按下标访问,还要能正确处理越界、找不到、异常等情况。


public class SafeEmployeeCollection
{
    private List<Employee> employees = new List<Employee>();

    public Employee this[int index]
    {
        get
        {
            if (index < 0 || index >= employees.Count)
                throw new IndexOutOfRangeException("没有这个下标的员工!");
            return employees[index];
        }
        set
        {
            if (index < 0 || index >= employees.Count)
                throw new IndexOutOfRangeException("不能替换不存在的员工!");
            employees[index] = value;
        }
    }
}

你可以返回null,也可以用现代的缺失值处理模式——看你应用逻辑怎么定。

6. 奇葩参数的索引器

你可能会遇到那种索引器参数不是常规类型的:比如枚举(enum)、自定义结构体,甚至多个不同类型的参数。


public enum Department { IT, HR, Finance, Marketing }

public class DepartmentEmployeeCollection
{
    private Dictionary<Department , Employee> departmentLeads = new Dictionary<Department , Employee>();

    public Employee this[Department  department]
    {
        get { return departmentLeads.TryGetValue(department, out var employee) ? employee : null; }
        set { departmentLeads[department] = value; }
    }
}
var company = new DepartmentEmployeeCollection();
company[Department.IT] = new Employee { Name = "安娜", Age = 35, Position = "IT主管" };
Employee itLead = company[Department.IT];

这种写法适合你有唯一标识符或者类型和值一一对应的场景——方便、直观又类型安全。

7. 现代特性:Range和Index

自从C# 8有了RangeIndex类型,索引器支持了区间倒序下标的新玩法:


public class SmartArray
{
    private int[] numbers = Enumerable.Range(0, 100).ToArray();

    public int[] this[Range range] => numbers[range];
    public int this[Index index] => numbers[index];
}

// 用法:
var smart = new SmartArray();
int[] middle = smart[20..30]; // 第20到29个元素
int last = smart[^1]; // 最后一个元素

如果你自己实现集合,支持RangeIndex会让你的类用起来特别“原生”又舒服。

8. 属性vs索引器

想搞清楚啥时候用属性,啥时候用索引器,最好对比下它们的特点。

  • 属性适合用名字访问对象的单独特性:person.Namecar.Speed
  • 索引器适合用key访问集合或结构里的元素:employees[0]phoneBook["伊万"]
  • 属性总有具体名字,一个类里同名属性只能有一个。
  • 索引器用关键字this,可以有多个重载,只要签名不同。
  • 属性可以是静态的,索引器不行,因为this总指向具体实例。

9. 用索引器常见的坑

坑1:没做边界检查。
如果你不检查下标越界,随时可能遇到IndexOutOfRangeException

坑2:没处理索引器返回的null
如果字符串索引器找不到返回null,调用方直接用就会NullReferenceException

坑3:索引器签名重复。
C#不允许有两个参数完全一样的索引器。

坑4:set里逻辑不直观。
如果set里是“没有就加,有就改”,容易让人懵。遇到这种最好写清楚注释。

10. 实战应用和总结

实际项目里,索引器经常用来做特殊集合、缓存、有额外逻辑的字典、矩阵、多维数据结构。它们让代码更直观易懂——不用写GetElementByIndex(5),直接collection[5]就行。

记住:索引器要和你的类本质逻辑一致。如果你的类不是集合或数据结构,可能根本不需要索引器。但如果你的类负责存和管一堆元素,索引器会让它用起来更方便、更自然。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION