C# 索引器(Indexer)

让自定义类型支持 `obj[i]` 语法,以及多维索引与接口实现。

Intermediate
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
2

String 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
张三
2

Interfaces 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.

Friends & Partners