目 录CONTENT

文章目录

CSharp(三十一) 索引器(Indexer)详解

CSharp(三十一) 索引器(Indexer)详解


目录

  1. 什么是索引器
  2. 为什么需要索引器
  3. 索引器的基本语法
  4. 一步步写出你的第一个索引器
  5. 索引器的工作原理
  6. 用不同参数类型做索引(重载)
  7. 多维索引器
  8. 只读索引器
  9. 表达式体索引器
  10. 在接口中定义索引器
  11. 配合泛型使用索引器
  12. 索引器 vs 数组 vs 属性
  13. 实战案例:自定义集合类
  14. 实战案例:双语词典
  15. 实战案例:电子表格
  16. 常见错误与注意事项
  17. 面试常考题
  18. 课后练习
  19. 小结

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
参数 方括号里放什么——可以是 intstring 等任意类型 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 参数吗?

:不可以。索引器的参数不能是 refout

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 方法,参数可以是任意类型。做集合、字典类时特别好用!


本文档专为教学编写,重在通俗易懂。如有疑问,建议动手敲一遍代码加深理解。

0

评论区