C# 索引器(Indexer)
让自定义类型支持 `obj[i]` 语法,以及多维索引与接口实现。
Key Points索引器就是带参数的属性,用 `this[...]` 声明。
基本写法
C#
class Week
{
private readonly string[] _days =
{
"周一", "周二", "周三", "周四", "周五", "周六", "周日"
};
public string this[int i]
{
get => _days[i];
set => _days[i] = value;
}
public int Length => _days.Length;
}
var w = new Week();
Console.WriteLine(w[0]);
Console.WriteLine(w[6]);
w[0] = "星期一";
Console.WriteLine(w[0]);Output
周一
周日
星期一An indexer is really get_Item(int) / set_Item(int, T), so w[0] compiles to w.get_Item(0). It can hold logic and validation.
带校验的索引器
C#
class SafeList
{
private readonly List<int> _items = new();
public int this[int i]
{
get
{
if (i < 0 || i >= _items.Count)
throw new IndexOutOfRangeException($"索引 {i} 超出范围 0~{_items.Count - 1}");
return _items[i];
}
set
{
if (i < 0) throw new ArgumentOutOfRangeException(nameof(i));
while (_items.Count <= i) _items.Add(0);
_items[i] = value;
}
}
public int Count => _items.Count;
}
var list = new SafeList();
list[2] = 42;
Console.WriteLine(list.Count);
Console.WriteLine(list[2]);
try
{
Console.WriteLine(list[10]);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine(ex.Message);
}Output
3
42
索引 10 超出范围 0~2字符串键索引器
C#
class Config
{
private readonly Dictionary<string, string> _values = new(StringComparer.OrdinalIgnoreCase);
public string this[string key]
{
get => _values.TryGetValue(key, out var v) ? v : "";
set => _values[key] = value;
}
public int Count => _values.Count;
}
var c = new Config();
c["Host"] = "example.com";
c["port"] = "8080";
Console.WriteLine(c["host"]); // 忽略大小写
Console.WriteLine(c["PORT"]);
Console.WriteLine(c["missing"] == "");
Console.WriteLine(c.Count);Output
example.com
8080
True
2String indexers are the usual shape for config objects and property bags. StringComparer.OrdinalIgnoreCase makes c["Host"] and c["host"] equivalent, dodging case bugs.
多维索引器
C#
class Matrix
{
private readonly double[,] _data;
public Matrix(int rows, int cols) => _data = new double[rows, cols];
public double this[int r, int c]
{
get => _data[r, c];
set => _data[r, c] = value;
}
public int Rows => _data.GetLength(0);
public int Cols => _data.GetLength(1);
}
var m = new Matrix(2, 3);
m[0, 0] = 1.5;
m[1, 2] = 9;
Console.WriteLine($"{m[0, 0]} {m[1, 2]}");
Console.WriteLine($"{m.Rows}x{m.Cols}");Output
1.5 9
2x3接口中的索引器
C#
interface IRepository<T>
{
T this[int id] { get; set; }
int Count { get; }
}
class MemoryRepo<T> : IRepository<T> where T : notnull
{
private readonly Dictionary<int, T> _store = new();
public T this[int id]
{
get => _store.TryGetValue(id, out var v) ? v : throw new KeyNotFoundException($"id {id} 不存在");
set => _store[id] = value;
}
public int Count => _store.Count;
}
IRepository<string> repo = new MemoryRepo<string>();
repo[1] = "张三";
repo[2] = "李四";
Console.WriteLine(repo[1]);
Console.WriteLine(repo.Count);Output
张三
2Interfaces can declare indexers and implementers must supply them. That gives "fetch entity by id" a uniform syntax — repo[1] beats repo.GetById(1).
Do not overuse indexers. When
obj[i] is ambiguous (order[3] — third item or id 3?), a named method reads better. Indexers suit types that are collections or maps.基本写法
C#
class Week
{
private readonly string[] _days =
{
"周一", "周二", "周三", "周四", "周五", "周六", "周日"
};
public string this[int i]
{
get => _days[i];
set => _days[i] = value;
}
public int Length => _days.Length;
}
var w = new Week();
Console.WriteLine(w[0]);
Console.WriteLine(w[6]);
w[0] = "星期一";
Console.WriteLine(w[0]);Output
周一
周日
星期一索引器本质是 get_Item(int) / set_Item(int, T) 方法,w[0] 编译后就是 w.get_Item(0)。所以它能有逻辑,也能做校验。
带校验的索引器
C#
class SafeList
{
private readonly List<int> _items = new();
public int this[int i]
{
get
{
if (i < 0 || i >= _items.Count)
throw new IndexOutOfRangeException($"索引 {i} 超出范围 0~{_items.Count - 1}");
return _items[i];
}
set
{
if (i < 0) throw new ArgumentOutOfRangeException(nameof(i));
while (_items.Count <= i) _items.Add(0);
_items[i] = value;
}
}
public int Count => _items.Count;
}
var list = new SafeList();
list[2] = 42;
Console.WriteLine(list.Count);
Console.WriteLine(list[2]);
try
{
Console.WriteLine(list[10]);
}
catch (IndexOutOfRangeException ex)
{
Console.WriteLine(ex.Message);
}Output
3
42
索引 10 超出范围 0~2字符串键索引器
C#
class Config
{
private readonly Dictionary<string, string> _values = new(StringComparer.OrdinalIgnoreCase);
public string this[string key]
{
get => _values.TryGetValue(key, out var v) ? v : "";
set => _values[key] = value;
}
public int Count => _values.Count;
}
var c = new Config();
c["Host"] = "example.com";
c["port"] = "8080";
Console.WriteLine(c["host"]); // 忽略大小写
Console.WriteLine(c["PORT"]);
Console.WriteLine(c["missing"] == "");
Console.WriteLine(c.Count);Output
example.com
8080
True
2字符串索引器是「配置对象」「动态属性包」的常见写法。用 StringComparer.OrdinalIgnoreCase 让 c["Host"] 和 c["host"] 等价,避免大小写导致的诡异 bug。
多维索引器
C#
class Matrix
{
private readonly double[,] _data;
public Matrix(int rows, int cols) => _data = new double[rows, cols];
public double this[int r, int c]
{
get => _data[r, c];
set => _data[r, c] = value;
}
public int Rows => _data.GetLength(0);
public int Cols => _data.GetLength(1);
}
var m = new Matrix(2, 3);
m[0, 0] = 1.5;
m[1, 2] = 9;
Console.WriteLine($"{m[0, 0]} {m[1, 2]}");
Console.WriteLine($"{m.Rows}x{m.Cols}");Output
1.5 9
2x3接口中的索引器
C#
interface IRepository<T>
{
T this[int id] { get; set; }
int Count { get; }
}
class MemoryRepo<T> : IRepository<T> where T : notnull
{
private readonly Dictionary<int, T> _store = new();
public T this[int id]
{
get => _store.TryGetValue(id, out var v) ? v : throw new KeyNotFoundException($"id {id} 不存在");
set => _store[id] = value;
}
public int Count => _store.Count;
}
IRepository<string> repo = new MemoryRepo<string>();
repo[1] = "张三";
repo[2] = "李四";
Console.WriteLine(repo[1]);
Console.WriteLine(repo.Count);Output
张三
2接口里可以声明索引器,实现类必须提供。这让「按 id 取实体」这类操作有了统一语法,repo[1] 比 repo.GetById(1) 更短。
索引器不要滥用。当
obj[i] 的语义不直观(比如 order[3] 到底是第 3 个商品还是 id=3 的订单?),用命名方法更清楚。索引器适合「本质是集合或字典」的类型。