索引器不是语法糖,而是编译器生成get_Item/set_Item方法的特殊属性;它要求this前必须声明返回类型、value仅在set中有效、参数类型不能为void,且多参数或非int索引器易引发性能与可维护性问题。

索引器不是语法糖,而是编译器生成 get_Item/set_Item 方法的特殊属性;写错会直接报 CS0103、CS0102 或运行时 IndexOutOfRangeException,不能靠“看起来像数组”就忽略边界和语义。
索引器基础写法里最容易写错的三处
新手常把索引器当成方法来写,结果编译失败。关键点只有三个:
-
this前面必须声明返回类型(比如string),但不能写public string GetItem(int index)—— 这是普通方法,不是索引器 -
value只在set里有效;在get里写return value;会触发CS0103 - 参数类型不能是
void,但可以是string、Guid、甚至DateTime,只要逻辑自洽
正确示例:
public class Cache
{
private Dictionary<string, object> _store = new();
public object this[string key] // 返回类型 object,参数 string
{
get => _store.TryGetValue(key, out var v) ? v : null;
set => _store[key] = value; // value 此处合法
}
}
多参数或非 int 索引器的实际代价
支持 obj["user", 123] 看起来很酷,但要注意它不是语法优化,而是性能和可维护性陷阱:
- 调用时参数顺序严格绑定:不能写成
obj[123, "user"],否则编译失败 - 如果内部用
FirstOrDefault查找,就是 O(n);而Dictionary<string, T>是 O(1),别用索引器掩盖低效设计 - 无法被 LINQ 的
ElementAt或Where直接识别——索引器只是语法糖,不是集合契约
更推荐的做法:该用字典就用字典,索引器只用于封装已有集合的访问逻辑,而不是替代数据结构。
接口和继承中索引器的隐性约束
接口定义索引器时只写签名,不实现;但实现类必须严格匹配可访问性:
- 接口里没写
get,实现类就不能只写set;反之亦然 - 子类重写父类索引器时,若用
base[index],得自己做越界检查——父类的检查不会自动继承 - 如果类同时实现两个含相同签名的接口(如
IReadOnlyList<T>和自定义IApiResponse<T>),必须用显式接口实现,例如:string IReadOnlyList<string>.this[int index] { get => ... }
另外注意:编译器默认为索引器生成名为 Item 的属性,如果你手动加了 public object Item { get; },会触发 CS0102;解决方法是加 [IndexerName("Data")] 改名。
为什么有时候索引器比方法更危险
因为它的调用形式太像数组,容易让人忽略副作用和异常边界:
-
list[100]看起来只是读,但如果内部做了远程缓存加载,可能引发网络异常或延迟 - 没有泛型约束时,
this[Guid id]的id若为default,可能静默查不到数据,而不是抛ArgumentNullException - 索引器不能标记为
async,所以无法自然支持异步加载逻辑;强行在get里await会导致编译错误
真正需要封装访问逻辑时,优先考虑显式方法(如 TryGetValue);只有当「类天然代表一个有序/键控集合」且调用方强烈期待数组式语法时,才用索引器——它是个契约,不是便利贴。


















