1. 多参数索引器
简单的索引器(比如this[int index])很适合你想用整数下标访问元素的时候——就像数组一样。但C#其实能做更多:你可以用多个参数、不同类型的参数、给get和set设置不同的访问修饰符,还能在一个类里实现多个索引器(只要签名不一样)。
而且新版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#里你可以给get和set分别设置不同的访问修饰符。
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有了Range和Index类型,索引器支持了区间和倒序下标的新玩法:
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]; // 最后一个元素
如果你自己实现集合,支持Range和Index会让你的类用起来特别“原生”又舒服。
8. 属性vs索引器
想搞清楚啥时候用属性,啥时候用索引器,最好对比下它们的特点。
- 属性适合用名字访问对象的单独特性:person.Name、car.Speed。
- 索引器适合用key访问集合或结构里的元素:employees[0]、phoneBook["伊万"]。
- 属性总有具体名字,一个类里同名属性只能有一个。
- 索引器用关键字this,可以有多个重载,只要签名不同。
- 属性可以是静态的,索引器不行,因为this总指向具体实例。
9. 用索引器常见的坑
坑1:没做边界检查。
如果你不检查下标越界,随时可能遇到IndexOutOfRangeException。
坑2:没处理索引器返回的null。
如果字符串索引器找不到返回null,调用方直接用就会NullReferenceException。
坑3:索引器签名重复。
C#不允许有两个参数完全一样的索引器。
坑4:set里逻辑不直观。
如果set里是“没有就加,有就改”,容易让人懵。遇到这种最好写清楚注释。
10. 实战应用和总结
实际项目里,索引器经常用来做特殊集合、缓存、有额外逻辑的字典、矩阵、多维数据结构。它们让代码更直观易懂——不用写GetElementByIndex(5),直接collection[5]就行。
记住:索引器要和你的类本质逻辑一致。如果你的类不是集合或数据结构,可能根本不需要索引器。但如果你的类负责存和管一堆元素,索引器会让它用起来更方便、更自然。
GO TO FULL VERSION