C#

C#每日面试题-索引器和迭代器的区别

C# 每日面试题:索引器(Indexer)和迭代器(Iterator)的区别

这是 C# 中非常高频的一道面试题,尤其在 .NET 中高级/资深岗位中,几乎每次都会问到,因为它考察了你对 C# 语言特性本质语法糖实现使用场景以及底层机制的理解。

下面从多个维度完整对比,方便你快速记忆和回答。

一、核心对比表(面试最常用回答框架)

维度索引器(Indexer)迭代器(Iterator)关键区别一句话总结
语法关键字this[参数]yield return / yield break
本质一种特殊的属性(Property-like)一种状态机生成器(编译器生成的 IEnumerator)属性 vs 方法生成器
作用对象类/结构体/接口方法(返回 IEnumerable 或 IEnumerator)作用于类型 vs 作用于方法
主要目的像数组一样用 [] 访问对象内部数据让对象能被 foreach 遍历自定义索引访问 vs 自定义遍历行为
返回类型任意类型(get 返回,set 接收)IEnumerable / IEnumerator / IAsyncEnumerable
是否可以有参数是(可以多个参数,甚至重载)通常无参(foreach 不传参)索引器可以参数化
是否可以只读/只写可以(只写 get 或只写 set)通常只读(yield return 产生值)索引器更灵活
编译后真实类型编译成 get_Item / set_Item 方法编译成状态机类(嵌套类 + MoveNext 实现)方法 vs 状态机
是否支持泛型支持支持(IEnumerable)
典型使用场景自定义集合、字典式访问、矩阵、配置项自定义集合、延迟计算、文件逐行读取、树遍历[] 访问 vs foreach 遍历
是否可以异步同步为主(C# 8+ 支持索引器 async get)支持 async(yield return + IAsyncEnumerable)迭代器异步更自然
性能开销几乎无(直接方法调用)有状态机开销(但现代 JIT 优化很好)索引器更轻量

二、最常见的面试追问 & 标准回答

Q1:请写出索引器和迭代器的典型代码

索引器示例(最常见写法)

public class StringDictionary
{
    private Dictionary<string, string> _data = new();

    // 索引器(最常见单参数形式)
    public string this[string key]
    {
        get => _data.TryGetValue(key, out var value) ? value : null;
        set => _data[key] = value;
    }

    // 支持多参数索引器(矩阵示例)
    public int this[int row, int col]
    {
        get => /* 计算逻辑 */;
        set => /* 计算逻辑 */;
    }
}

// 使用
var dict = new StringDictionary();
dict["name"] = "张三";           // 调用 set_Item
string name = dict["name"];       // 调用 get_Item

迭代器示例(最常见两种写法)

// 方式1:返回 IEnumerable<T>(最常用)
public IEnumerable<int> GetFibonacci(int count)
{
    int a = 0, b = 1;
    for (int i = 0; i < count; i++)
    {
        yield return a;
        int temp = a + b;
        a = b;
        b = temp;
    }
}

// 方式2:返回 IEnumerator<T>(较少见,手动控制)
public IEnumerator<int> GetEnumerator()
{
    yield return 1;
    yield return 1;
    yield return 2;
    yield return 3;
    // ...
}

// 使用
foreach (var num in GetFibonacci(10))
{
    Console.WriteLine(num);
}

Q2:索引器底层编译成什么?迭代器底层编译成什么?

  • 索引器 → 编译成两个方法:
  • public string get_Item(string key)
  • public void set_Item(string key, string value)
  • 迭代器 → 编译器生成一个状态机类(嵌套的密封类),实现 IEnumerator<T> 接口,包含:
  • 状态字段(-1=初始,0=开始,1=第一个 yield,…)
  • MoveNext() 方法(核心状态跳转逻辑)
  • Current 属性
  • Dispose 方法(用于 finally 块)

Q3:什么时候用索引器?什么时候用迭代器?

  • 用索引器
  • 想让你的类像数组/字典一样使用 obj[key] 访问
  • 需要对内部集合进行封装访问(类似 Dictionary、List)
  • 多维索引(如矩阵、表格、配置表)
  • 用迭代器
  • 需要让你的类支持 foreach 遍历
  • 想实现延迟计算(yield return 按需产生元素)
  • 处理大数据量(如逐行读取文件、遍历树、生成无穷序列)
  • 希望代码写得更简洁(避免手动实现 IEnumerator)

Q4:索引器和属性(Property)的本质区别?

  • 本质相同:索引器就是带参数的属性
  • 语法区别:属性用 . 访问,索引器用 [] 访问
  • 使用习惯:属性通常表示“成员”,索引器通常表示“集合中的元素”

Q5:C# 中支持异步迭代器的场景?

C# 8.0+ 支持 IAsyncEnumerable<T> + yield return

public async IAsyncEnumerable<int> GetNumbersAsync()
{
    for (int i = 0; i < 10; i++)
    {
        await Task.Delay(100);
        yield return i;
    }
}

// 使用
await foreach (var num in GetNumbersAsync())
{
    Console.WriteLine(num);
}

三、总结一句话回答模板(面试 30 秒版本)

索引器是让类支持像数组一样使用 obj[key] 访问的语法糖,本质是编译成 get_Item / set_Item 方法,主要用于自定义集合的元素访问;迭代器是让类支持 foreach 遍历的语法糖,通过 yield return 实现,本质是编译器生成一个状态机类(实现 IEnumerator),主要用于延迟计算和自定义遍历逻辑。简单说:索引器 = 自定义 [] 访问,迭代器 = 自定义 foreach 行为。”

需要我继续补充:

  • 索引器重载、接口索引器写法
  • 迭代器在 LINQ 中的作用
  • yield return 的性能分析与状态机反编译
  • 实际项目中常见的坑

告诉我,我们继续深入!

分类: C#
文章已创建 4138

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部