CSharp(三十一) 索引器(Indexer)详解
目录
- 什么是索引器
- 为什么需要索引器
- 索引器的基本语法
- 一步步写出你的第一个索引器
- 索引器的工作原理
- 用不同参数类型做索引(重载)
- 多维索引器
- 只读索引器
- 表达式体索引器
- 在接口中定义索引器
- 配合泛型使用索引器
- 索引器 vs 数组 vs 属性
- 实战案例:自定义集合类
- 实战案例:双语词典
- 实战案例:电子表格
- 常见错误与注意事项
- 面试常考题
- 课后练习
- 小结
1. 什么是索引器
一个生活的比喻
想象你有一个储物柜墙。每个柜子都有编号——1号、2号、3号……
柜子编号: [0] [1] [2] [3] [4]
存放物品: 📕 📗 📘 📙 📓
当你想要取东西时,只需要说"打开第0号柜子",不用记柜子里装的是什么名字。这就是索引的思想——通过编号(或键)直接访问数据。
在 C# 里,索引器(Indexer) 就是让一个对象可以像数组一样,用 [] 方括号来访问它内部的数据。
代码世界的类比
// 访问数组 → 用方括号
int[] arr = { 10, 20, 30 };
int value = arr[1]; // 拿到 20
// 有了索引器,你自己的类也能这么用!
MyCollection mc = new MyCollection();
mc[0] = "Hello"; // 像数组一样存数据
string s = mc[0]; // 像数组一样取数据
一句话总结:索引器让你自定义的类也能用
对象[索引]的方式来读写数据,就像操作数组一样自然。
2. 为什么需要索引器
没有索引器的世界
假设你要写一个班级类,里面存放多个学生:
public class ClassRoom
{
private List<string> students = new List<string>();
// 没有索引器,只能写一堆方法来访问
public string GetStudent(int index)
{
return students[index];
}
public void SetStudent(int index, string name)
{
students[index] = name;
}
}
// 使用时很啰嗦
ClassRoom room = new ClassRoom();
room.SetStudent(0, "张三");
string name = room.GetStudent(0);
有索引器的世界
public class ClassRoom
{
private List<string> students = new List<string>();
// 有了索引器,一个 this[] 搞定
public string this[int index]
{
get { return students[index]; }
set { students[index] = value; }
}
}
// 使用时像数组一样简洁
ClassRoom room = new ClassRoom();
room[0] = "张三"; // 直接赋值
string name = room[0]; // 直接取值
索引器让你的代码更直观、更简洁,别人用你的类时不会觉得别扭。
3. 索引器的基本语法
语法骨架
访问修饰符 返回类型 this[参数类型 参数名]
{
get
{
// 读取逻辑 → return 某个值;
}
set
{
// 写入逻辑 → 用 value 接收外部传进来的值
}
}
每个部分解读
| 部分 | 说明 | 示例 |
|---|---|---|
| 访问修饰符 | public, private 等,控制谁能用这个索引器 |
public |
| 返回类型 | 读取时 return 的值的类型 | string, int 等 |
this |
固定写法,表示"当前对象"的索引器 | 必须写 this |
| 参数 | 方括号里放什么——可以是 int、string 等任意类型 |
int index |
get |
读取时执行的代码块 | 必须 return 一个值 |
set |
赋值时执行的代码块 | value 是传入的值 |
可视化理解
使用代码: 索引器内部:
obj[5] = "Hello"; ──────▶ set { 把 "Hello" 存到 value 变量里 }
string s = obj[5]; ──────▶ get { 找到位置5的数据, return 出去 }
4. 一步步写出你的第一个索引器
场景:做一个简单的字符串仓库
我们做一个类,里面存 5 个字符串,可以用索引器访问。
using System;
// 第1步:定义一个类
public class StringStore
{
// 第2步:类内部用一个数组存数据
private string[] data = new string[5];
// 第3步:写索引器
public string this[int i]
{
get
{
// i 是用户传进来的索引
// 把数组第 i 个元素返回给用户
return data[i];
}
set
{
// value 是用户赋值的内容
// 把 value 存到数组第 i 个位置
data[i] = value;
}
}
}
// 第4步:在 Main 里使用
class Program
{
static void Main()
{
StringStore store = new StringStore();
store[0] = "苹果"; // 调用 set,value = "苹果"
store[1] = "香蕉"; // 调用 set,value = "香蕉"
store[2] = "橘子"; // 调用 set,value = "橘子"
Console.WriteLine(store[0]); // 调用 get,输出:苹果
Console.WriteLine(store[1]); // 调用 get,输出:香蕉
Console.WriteLine(store[2]); // 调用 get,输出:橘子
}
}
加上边界检查(安全版本)
上面的代码有个隐患:如果用户传了 store[100],程序会崩溃。我们来改进它:
public string this[int i]
{
get
{
// 先检查索引是否合法
if (i >= 0 && i < data.Length)
return data[i]; // 合法 → 返回数据
else
throw new IndexOutOfRangeException($"索引{i}超出范围!");
}
set
{
if (i >= 0 && i < data.Length)
data[i] = value; // 合法 → 存入数据
else
throw new IndexOutOfRangeException($"索引{i}超出范围!");
}
}
5. 索引器的工作原理
背后是什么?
索引器本质上是一对方法。编译器会自动把它翻译成两个方法:
// 你写的索引器
public string this[int i]
{
get { return data[i]; }
set { data[i] = value; }
}
// 编译器实际生成的(伪代码示意,不是真正的C#)
public string get_Item(int i) // get 变成一个方法
{
return data[i];
}
public void set_Item(int i, string value) // set 变成另一个方法
{
data[i] = value;
}
所以当你写 store[0] = "苹果" 时,实际调用的是 store.set_Item(0, "苹果")。当你写 Console.WriteLine(store[0]) 时,实际调用的是 store.get_Item(0)。
完整执行流程图
用户写: 编译器处理: 实际执行:
store[2] = "橘子" → 调用 set_Item(2, "橘子") → data[2] = "橘子"
string s = store[2] → 调用 get_Item(2) → return data[2]
6. 用不同参数类型做索引(重载)
索引器支持重载(Overload)——你可以定义多个索引器,用不同的参数类型来区分。
场景:按编号查找 + 按名字查找
public class StudentList
{
// 内部用字典存数据:学号 → 姓名
private Dictionary<int, string> students = new Dictionary<int, string>();
// 索引器1:按学号(int)查找姓名
public string this[int studentId]
{
get
{
if (students.ContainsKey(studentId))
return students[studentId];
else
return "未找到该学生";
}
set
{
students[studentId] = value;
}
}
// 索引器2:按姓名(string)查找学号
// 返回 -1 表示没找到
public int this[string name]
{
get
{
foreach (var pair in students)
{
if (pair.Value == name)
return pair.Key; // 返回学号
}
return -1; // 没找到
}
// 注意:这个索引器只有 get,没有 set(不可通过姓名设置)
}
}
// 使用
class Program
{
static void Main()
{
StudentList list = new StudentList();
// 用 int 做索引 → 调用索引器1
list[1001] = "张三";
list[1002] = "李四";
list[1003] = "王五";
Console.WriteLine(list[1001]); // 输出:张三 (按学号查姓名)
Console.WriteLine(list[1002]); // 输出:李四
// 用 string 做索引 → 调用索引器2
Console.WriteLine(list["李四"]); // 输出:1002 (按姓名查学号)
Console.WriteLine(list["赵六"]); // 输出:-1 (没找到)
}
}
关键规则:重载索引器时,参数类型(或参数个数)必须不同。不能有两个都是
int参数的索引器。
7. 多维索引器
索引器可以接受多个参数,实现类似二维数组的效果。
场景:棋盘游戏
public class ChessBoard
{
// 8×8 的棋盘,用二维数组存棋子
private string[,] board = new string[8, 8];
// 二维索引器:用行号和列号访问
public string this[int row, int col]
{
get
{
if (row < 0 || row >= 8 || col < 0 || col >= 8)
throw new ArgumentException("坐标超出棋盘范围!");
return board[row, col] ?? "空";
}
set
{
if (row < 0 || row >= 8 || col < 0 || col >= 8)
throw new ArgumentException("坐标超出棋盘范围!");
board[row, col] = value;
}
}
}
// 使用
class Program
{
static void Main()
{
ChessBoard chess = new ChessBoard();
chess[0, 0] = "车"; // 左下角放车
chess[0, 1] = "马"; // 旁边放马
chess[7, 7] = "王"; // 右上角放王
Console.WriteLine(chess[0, 0]); // 输出:车
Console.WriteLine(chess[0, 1]); // 输出:马
Console.WriteLine(chess[3, 3]); // 输出:空(没放棋子)
}
}
场景:矩阵运算
public class Matrix
{
private int[,] data;
public Matrix(int rows, int cols)
{
data = new int[rows, cols];
}
public int Rows => data.GetLength(0);
public int Cols => data.GetLength(1);
// 二维索引器
public int this[int r, int c]
{
get => data[r, c];
set => data[r, c] = value;
}
// 打印矩阵
public void Print()
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Cols; j++)
{
Console.Write(data[i, j].ToString().PadLeft(4));
}
Console.WriteLine();
}
}
}
// 使用
Matrix m = new Matrix(3, 3);
m[0, 0] = 1; m[0, 1] = 2; m[0, 2] = 3;
m[1, 0] = 4; m[1, 1] = 5; m[1, 2] = 6;
m[2, 0] = 7; m[2, 1] = 8; m[2, 2] = 9;
m.Print();
// 输出:
// 1 2 3
// 4 5 6
// 7 8 9
8. 只读索引器
有时候你只希望外部能读,不能改。那就只写 get,不写 set。
public class WeekDays
{
private string[] days = { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
// 只读索引器:只有 get,没有 set
public string this[int index]
{
get
{
index = index % 7; // 支持循环访问(7→0, 8→1...)
return days[index];
}
// 没有 set → 外部不能修改
}
}
// 使用
WeekDays wd = new WeekDays();
Console.WriteLine(wd[0]); // 输出:周一
Console.WriteLine(wd[6]); // 输出:周日
// wd[0] = "星期零"; // ❌ 编译错误!没有 set
使用 Lambda 简化(C# 7.0+)
如果 get 体只有一行,可以用表达式体:
// 简化写法(效果完全一样)
public string this[int i] => days[i % 7];
9. 表达式体索引器
C# 7.0 开始支持用 => 写更简洁的索引器:
public class SimpleList
{
private List<string> items = new List<string>();
// get 用表达式体
public string this[int i] => items[i];
// 也可以在 set 用表达式体
// 但通常 set 有多行逻辑时不适用
}
// 如果 set 也很简单(C# 7.0+)
public class Point3D
{
private double[] coords = new double[3];
public double this[int axis]
{
get => coords[axis];
set => coords[axis] = value;
}
}
经验:表达式体适合简单的取值/赋值逻辑,复杂的还是用完整写法好读。
10. 在接口中定义索引器
索引器可以出现在接口里,要求实现类必须提供索引器功能。
// 定义接口:要求实现类能用索引访问
public interface IDataStore
{
// 接口中只声明,不实现
string this[int id] { get; set; }
}
// 实现接口
public class FileDataStore : IDataStore
{
private Dictionary<int, string> data = new Dictionary<int, string>();
// 必须实现索引器
public string this[int id]
{
get => data.ContainsKey(id) ? data[id] : null;
set => data[id] = value;
}
}
public class MemoryDataStore : IDataStore
{
private string[] cache = new string[1000];
public string this[int id]
{
get => cache[id];
set => cache[id] = value;
}
}
// 多态使用
IDataStore store = new FileDataStore(); // 或 new MemoryDataStore()
store[1] = "数据A";
Console.WriteLine(store[1]);
11. 配合泛型使用索引器
索引器和泛型一起用,可以做出通用的数据结构:
// 泛型集合:能存任意类型的数据
public class MyList<T>
{
private T[] items = new T[10];
private int count = 0;
// 泛型索引器
public T this[int index]
{
get
{
if (index < 0 || index >= count)
throw new IndexOutOfRangeException();
return items[index];
}
set
{
if (index < 0 || index >= items.Length)
throw new IndexOutOfRangeException();
items[index] = value;
if (index >= count)
count = index + 1;
}
}
}
// 使用
MyList<int> numbers = new MyList<int>();
numbers[0] = 42;
numbers[1] = 100;
Console.WriteLine(numbers[0]); // 输出:42
Console.WriteLine(numbers[1]); // 输出:100
MyList<string> names = new MyList<string>();
names[0] = "张三";
names[1] = "李四";
12. 索引器 vs 数组 vs 属性
很多初学者容易混淆这三者,我们用一张表来区分:
| 特性 | 索引器 | 数组 | 属性 |
|---|---|---|---|
| 语法 | this[参数] |
变量名[下标] |
属性名 { get; set; } |
| 参数个数 | 1个或多个 | 每个维度1个下标 | 不需要参数 |
| 参数类型 | 任意类型(int/string等) | 只能是整数 | 无参数 |
| 属于 | 类或结构体 | 独立的类型 | 类或结构体 |
| 访问方式 | 对象[参数] |
数组名[整数] |
对象.属性名 |
| 本质 | 一对 get/set 方法 | 连续内存块 | 一对 get/set 方法 |
什么时候用索引器,什么时候用属性?
public class Student
{
// ✅ 属性:表示对象的"特征"
public string Name { get; set; }
public int Age { get; set; }
// 如果只是单个值,用属性就够了,不需要索引器
}
public class ClassRoom
{
private string[] studentNames = new string[50];
// ✅ 索引器:表示"按编号访问集合里的成员"
public string this[int index]
{
get => studentNames[index];
set => studentNames[index] = value;
}
// ❌ 不要写成属性:public string Student1, Student2, Student3...
// 那样太蠢了,50个学生要写50个属性
}
简单记忆:一个值 → 属性;一堆值中的一个 → 索引器。
13. 实战案例:自定义集合类
我们来做一个完整的学生花名册,用到索引器的所有知识:
using System;
using System.Collections.Generic;
/// <summary>
/// 学生花名册 —— 一个支持索引器的集合类
/// </summary>
public class StudentRoster
{
// 内部用 List 存学生对象
private List<Student> students = new List<Student>();
// ==================== 索引器1:按位置(int)访问 ====================
public Student this[int index]
{
get
{
if (index < 0 || index >= students.Count)
throw new IndexOutOfRangeException(
$"花名册只有{students.Count}人,索引{index}无效");
return students[index];
}
set
{
if (index < 0 || index >= students.Count)
throw new IndexOutOfRangeException(
$"花名册只有{students.Count}人,索引{index}无效");
students[index] = value;
}
}
// ==================== 索引器2:按学号(string)查找 ====================
public Student this[string studentId]
{
get
{
foreach (var s in students)
{
if (s.Id == studentId)
return s;
}
return null; // 没找到返回 null
}
}
// ==================== 索引器3:按成绩等级查找所有符合的人 ====================
// 参数:(char grade, bool exactMatch) — 多个参数!
public List<Student> this[char grade, bool exactMatch]
{
get
{
List<Student> result = new List<Student>();
foreach (var s in students)
{
if (s.Grade == grade)
result.Add(s);
}
return result;
}
}
// ==================== 辅助方法 ====================
public void Add(Student student)
{
students.Add(student);
}
public void RemoveAt(int index)
{
students.RemoveAt(index);
}
public int Count => students.Count;
}
/// <summary>
/// 学生类
/// </summary>
public class Student
{
public string Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public char Grade { get; set; } // A, B, C, D, F
public override string ToString()
{
return $"[{Id}] {Name}, {Age}岁, 等级:{Grade}";
}
}
// ==================== 使用演示 ====================
class Program
{
static void Main()
{
StudentRoster roster = new StudentRoster();
// 添加学生
roster.Add(new Student { Id = "2024001", Name = "张三", Age = 20, Grade = 'A' });
roster.Add(new Student { Id = "2024002", Name = "李四", Age = 21, Grade = 'B' });
roster.Add(new Student { Id = "2024003", Name = "王五", Age = 19, Grade = 'A' });
roster.Add(new Student { Id = "2024004", Name = "赵六", Age = 22, Grade = 'C' });
// ===== 索引器1:按位置访问 =====
Console.WriteLine("=== 按位置访问 ===");
Console.WriteLine(roster[0]); // 输出:[2024001] 张三, 20岁, 等级:A
Console.WriteLine(roster[2]); // 输出:[2024003] 王五, 19岁, 等级:A
// 修改指定位置的学生
roster[1] = new Student { Id = "2024005", Name = "替换者", Age = 23, Grade = 'D' };
Console.WriteLine(roster[1]); // 输出:[2024005] 替换者, 23岁, 等级:D
// ===== 索引器2:按学号查找 =====
Console.WriteLine("\n=== 按学号查找 ===");
Student found = roster["2024003"];
if (found != null)
Console.WriteLine($"找到了:{found.Name}");
else
Console.WriteLine("没找到该学生");
// ===== 索引器3:按等级筛选 =====
Console.WriteLine("\n=== 等级为A的学生 ===");
List<Student> aStudents = roster['A', true];
foreach (var s in aStudents)
{
Console.WriteLine($" {s.Name}");
}
}
}
14. 实战案例:双语词典
做一个简单的中英词典,中文查英文、英文查中文:
using System;
using System.Collections.Generic;
public class BilingualDictionary
{
// 两个方向的字典
private Dictionary<string, string> cn2en = new Dictionary<string, string>();
private Dictionary<string, string> en2cn = new Dictionary<string, string>();
// 添加词条
public void Add(string chinese, string english)
{
cn2en[chinese] = english;
en2cn[english] = chinese;
}
// ========== 索引器1:方向参数(Direction枚举)==========
public string this[string word, Direction direction]
{
get
{
if (direction == Direction.CN2EN)
{
return cn2en.ContainsKey(word) ? cn2en[word] : "未收录";
}
else // EN2CN
{
return en2cn.ContainsKey(word) ? en2cn[word] : "未收录";
}
}
}
// ========== 索引器2:自动识别方向(中文→英文 或 英文→中文)==========
// 思路:看第一个字符是不是英文字母
public string this[string word]
{
get
{
// 如果首字符是英文字母,按英文查中文
if (word.Length > 0 && char.IsLetter(word[0]) && word[0] <= 127)
{
return en2cn.ContainsKey(word) ? en2cn[word] : "未收录";
}
else // 否则按中文查英文
{
return cn2en.ContainsKey(word) ? cn2en[word] : "未收录";
}
}
}
}
public enum Direction
{
CN2EN, // 中→英
EN2CN // 英→中
}
// 使用
class Program
{
static void Main()
{
BilingualDictionary dict = new BilingualDictionary();
dict.Add("苹果", "apple");
dict.Add("香蕉", "banana");
dict.Add("猫", "cat");
// 按方向查
Console.WriteLine(dict["苹果", Direction.CN2EN]); // 输出:apple
Console.WriteLine(dict["cat", Direction.EN2CN]); // 输出:猫
// 自动识别
Console.WriteLine(dict["香蕉"]); // 输出:banana (自动识别为中文查英文)
Console.WriteLine(dict["apple"]); // 输出:苹果 (自动识别为英文查中文)
Console.WriteLine(dict["狗"]); // 输出:未收录
}
}
15. 实战案例:电子表格
模拟一个简化版的电子表格:
using System;
using System.Collections.Generic;
public class Spreadsheet
{
private Dictionary<string, string> cells = new Dictionary<string, string>();
// 列标:A, B, C, D...
// 行号:1, 2, 3, 4...
//
// ========== 索引器1:用字符串坐标访问(如 "A1", "B5")==========
public string this[string cellRef]
{
get
{
// 如果单元格存在,返回值;否则返回空字符串
return cells.ContainsKey(cellRef) ? cells[cellRef] : "";
}
set
{
cells[cellRef] = value;
}
}
// ========== 索引器2:用列+行数字访问(如 (0,0) 代表 A1)==========
public string this[int col, int row]
{
get
{
// 把数字列号转为字母:0→A, 1→B, 2→C...
string cellRef = $"{(char)('A' + col)}{row + 1}";
return this[cellRef]; // 委托给索引器1
}
set
{
string cellRef = $"{(char)('A' + col)}{row + 1}";
this[cellRef] = value;
}
}
// 打印整个表格
public void Print(int cols, int rows)
{
Console.Write(" "); // 表头缩进
for (int c = 0; c < cols; c++)
Console.Write($" {(char)('A' + c)} ");
Console.WriteLine();
for (int r = 0; r < rows; r++)
{
Console.Write($"{r + 1,3} "); // 行号
for (int c = 0; c < cols; c++)
{
string val = this[c, r];
Console.Write($" {val,3} ");
}
Console.WriteLine();
}
}
}
// 使用
class Program
{
static void Main()
{
Spreadsheet sheet = new Spreadsheet();
// 用字母坐标
sheet["A1"] = "姓名";
sheet["B1"] = "成绩";
sheet["A2"] = "张三";
sheet["B2"] = "92";
// 用数字坐标
sheet[0, 2] = "李四"; // A3
sheet[1, 2] = "85"; // B3
// 读取
Console.WriteLine(sheet["A1"]); // 输出:姓名
Console.WriteLine(sheet[0, 0]); // 输出:姓名(和 A1 相同)
sheet.Print(3, 4);
// 输出:
// A B C
// 1 姓名 成绩
// 2 张三 92
// 3 李四 85
// 4
}
}
16. 常见错误与注意事项
错误1:忘记边界检查
// ❌ 危险:用户传 -1 或 9999 会直接崩溃
public string this[int i]
{
get { return data[i]; }
set { data[i] = value; }
}
// ✅ 安全:先检查再操作
public string this[int i]
{
get
{
if (i < 0 || i >= data.Length)
throw new IndexOutOfRangeException($"索引{i}超出范围");
return data[i];
}
set
{
if (i < 0 || i >= data.Length)
throw new IndexOutOfRangeException($"索引{i}超出范围");
data[i] = value;
}
}
错误2:索引器不能是静态的
// ❌ 编译错误!索引器必须是实例成员
// public static string this[int i] { get; set; }
索引器需要操作实例内部的数据,所以必须是实例成员(不能用 static)。
错误3:滥用索引器
public class Person
{
// ❌ 不合适!人的属性应该用普通属性
public string this[string field]
{
get
{
if (field == "name") return Name;
if (field == "age") return Age.ToString();
return "";
}
}
// ✅ 正确的做法
public string Name { get; set; }
public int Age { get; set; }
}
原则:索引器用于"集合/容器"类,普通对象用属性。
错误4:索引器访问修饰符不合理
// ⚠️ 一般索引器用 public,很少用 private
// 因为索引器就是为了让外部方便访问的
public string this[int i] { get; set; } // ✅ 常见的
// private string this[int i] { get; set; } // 很少见(那为啥不用字段?)
17. 面试常考题
Q1:索引器和属性的区别是什么?
答:
| 特性 | 属性 | 索引器 |
|---|---|---|
| 访问方式 | 对象.属性名 |
对象[参数] |
| 本质 | get/set 方法 | get/set 方法 |
| 参数 | 不需要 | 必须至少1个参数 |
| 名字 | 有名字 | 固定叫 this |
| 静态支持 | 可以静态 | 不能静态 |
Q2:一个类可以有多个索引器吗?
答:可以。只要参数列表(类型、个数)不同,就能重载。
public string this[int i] { get; set; } // 索引器1
public string this[string s] { get; set; } // 索引器2(参数类型不同)
public string this[int i, int j] { get; } // 索引器3(参数个数不同)
Q3:索引器可以有 out/ref 参数吗?
答:不可以。索引器的参数不能是 ref 或 out。
Q4:"索引器可以是虚方法吗?"
答:可以。索引器可以用 virtual 修饰,派生类用 override 重写。
public class Base
{
public virtual string this[int i] => "Base";
}
public class Derived : Base
{
public override string this[int i] => "Derived";
}
18. 课后练习
练习1:温度记录器
做一个 TemperatureLog 类,记录一周7天的温度,用索引器按星期几(int: 0=周一 ~ 6=周日)访问。
// 要求:
// 1. 用 double[] 存7天的温度
// 2. 索引器 get 返回对应温度,set 设置温度
// 3. 超出范围抛出异常
// 期望效果:
// TemperatureLog log = new TemperatureLog();
// log[0] = 25.5; // 周一温度
// log[1] = 26.0; // 周二温度
// Console.WriteLine(log[0]); // 输出:25.5
点击查看参考代码
public class TemperatureLog
{
private double[] temps = new double[7];
public double this[int day]
{
get
{
if (day < 0 || day > 6)
throw new ArgumentOutOfRangeException("请输入0~6(周一到周日)");
return temps[day];
}
set
{
if (day < 0 || day > 6)
throw new ArgumentOutOfRangeException("请输入0~6(周一到周日)");
temps[day] = value;
}
}
}
练习2:简单电话本
做一个 PhoneBook 类,能按姓名查电话,也能按电话查姓名。
// 要求:
// 1. 索引器1:this[string name] → 返回电话号码
// 2. 索引器2:this[int phone] → 返回姓名
// 3. 找不到时返回 "未找到"
// 期望效果:
// PhoneBook pb = new PhoneBook();
// pb["张三"] = "13800138000";
// pb["李四"] = "13900139000";
// Console.WriteLine(pb["张三"]); // 输出:13800138000
// Console.WriteLine(pb[13900139000]); // 输出:李四
点击查看参考代码
public class PhoneBook
{
private Dictionary<string, long> nameToPhone = new Dictionary<string, long>();
private Dictionary<long, string> phoneToName = new Dictionary<long, string>();
public long this[string name]
{
get
{
return nameToPhone.ContainsKey(name) ? nameToPhone[name] : -1;
}
set
{
nameToPhone[name] = value;
phoneToName[value] = name;
}
}
public string this[long phone]
{
get
{
return phoneToName.ContainsKey(phone) ? phoneToName[phone] : "未找到";
}
}
}
练习3:游戏背包
做一个 Inventory 背包类,可以放10个物品(物品只是字符串名字)。
// 要求:
// 1. 索引器按格子编号(0~9)存取物品
// 2. 支持把物品放到指定格子和取出
// 3. 超出范围或格子为空要有提示
// 额外挑战:加一个按物品名查找的索引器(返回所在格子编号)
19. 小结
┌──────────────────────────────────────────────────────┐
│ 索引器核心要点 │
├──────────────────────────────────────────────────────┤
│ 定义: public 返回类型 this[参数] { get; set; } │
│ │
│ 本质: 一对 get_Item / set_Item 方法 │
│ │
│ 用途: 让对象像数组一样用 [] 访问数据 │
│ │
│ 重载: 不同参数类型/个数,可以有多个索引器 │
│ │
│ 限制: 不能是静态的,不能有 ref/out 参数 │
│ │
│ 场景: 集合类、字典类、矩阵类、配置类等 │
│ │
│ 对比: 单个值 → 用属性 / 一堆值 → 用索引器 │
└──────────────────────────────────────────────────────┘
一句话总结
索引器就是让你的类也能用
对象[下标]的方式操作内部数据,本质是特殊的 get/set 方法,参数可以是任意类型。做集合、字典类时特别好用!
本文档专为教学编写,重在通俗易懂。如有疑问,建议动手敲一遍代码加深理解。
评论区