C# - 索引器
C# - 索引器
Section titled “C# - 索引器”索引器(Indexer)是一种特殊的成员,它允许对象像数组一样通过索引进行访问。当你为一个类定义索引器时,可以使用熟悉的数组访问运算符([])来访问该类的实例。这为访问自定义集合或数据结构中的元素提供了一种更直观、更富有表现力的语法。
一维索引器的语法与属性(property)类似,但它使用 this 关键字作为名称,并在方括号中包含一个索引参数。
public T this[int index]{ // get 访问器用于读取值。 get { // 返回指定索引处值的逻辑。 }
// set 访问器用于赋值。 set { // 设置指定索引处值的逻辑。 }}与属性一样,索引器可以包含 get 和/或 set 访问器。你还可以使用表达式主体语法(expression-bodied syntax)来实现更简洁的代码,这在现代 C# 中很常见。
核心概念与用例
Section titled “核心概念与用例”属性(property)用于访问特定的、命名的成员变量,而索引器(indexer)则用于通过键(可以是整数、字符串或其他类型)访问集合状对象中的元素。这有效地抽象了类的内部存储机制,使其可以像虚拟数组或字典一样行为。
示例:一个简单的 Sentence 类
Section titled “示例:一个简单的 Sentence 类”让我们创建一个 Sentence 类,用于存储单词的集合。我们将添加一个索引器,以便通过位置访问单个单词。此示例使用了现代 C# 特性,例如表达式主体成员(expression-bodied members)和适当的异常处理(exception handling)。
using System;
public class Sentence{ private readonly string[] _words;
public Sentence(string text) { _words = text.Split(' '); }
// 用于通过整数位置访问单词的索引器 public string this[int index] { get { if (index < 0 || index >= _words.Length) { throw new IndexOutOfRangeException($"Index {index} is out of range."); } return _words[index]; } set { if (index < 0 || index >= _words.Length) { throw new IndexOutOfRangeException($"Index {index} is out of range."); } _words[index] = value; } }
public int Length => _words.Length;}
public class Program{ public static void Main(string[] args) { var sentence = new Sentence("The quick brown fox jumps over the lazy dog");
// Using the 'get' accessor of the indexer Console.WriteLine($"The fourth word is: {sentence[3]}"); // 输出: fox
// Using the 'set' accessor of the indexer sentence[1] = "slow";
Console.WriteLine("The modified sentence is:"); for (int i = 0; i < sentence.Length; i++) { Console.Write($"{sentence[i]} "); } // 输出: The slow brown fox jumps over the lazy dog }}最佳实践:异常处理
Section titled “最佳实践:异常处理”在前面的示例中,我们检查了索引,如果索引无效则抛出 IndexOutOfRangeException。这是标准做法,与内置 .NET 集合的行为方式一致。返回 null 或空字符串可能会隐藏错误,并导致代码后面出现意料之外的 NullReferenceException。
使用不同的索引类型
Section titled “使用不同的索引类型”索引器不限于整数键。你可以使用任何类型,例如 string,来创建类似字典的行为。这通常通过使用 Dictionary<TKey, TValue> 作为内部数据存储来实现。
示例:字典式配置管理器
Section titled “示例:字典式配置管理器”下面是一个简单的配置管理器示例,它使用字符串索引器来获取和设置配置值。
using System;using System.Collections.Generic;
public class AppSettings{ private readonly Dictionary<string, string> _settings = new();
// 字符串索引器 public string? this[string key] { get { // TryGetValue 比先检查 ContainsKey 再访问键更高效 _settings.TryGetValue(key, out var value); return value; // 如果键未找到,则返回 null } set { if (value is null) { _settings.Remove(key); } else { _settings[key] = value; } } }}
public class Program{ public static void Main() { var settings = new AppSettings();
// 使用 set 访问器 settings["ApiUrl"] = "https://api.example.com"; settings["Timeout"] = "30000";
// 使用 get 访问器 Console.WriteLine($"API URL: {settings["ApiUrl"]}"); Console.WriteLine($"Non-existent Key: {settings["ApiKey"] ?? "Not Found"}"); }}一个类可以有多个索引器,这一特性称为索引器重载(indexer overloading)。每个重载都必须具有不同数量或类型的参数。这允许以灵活的方式访问数据。
示例:一个 2D 网格
Section titled “示例:一个 2D 网格”代表 2D 网格的类是索引器重载的完美候选。我们可以提供一个索引器来访问特定的单元格 (row, col),并提供另一个来访问整个行。
using System;
public class Grid<T>{ private readonly T[,] _matrix;
public Grid(int rows, int cols) { if (rows <= 0) throw new ArgumentOutOfRangeException(nameof(rows)); if (cols <= 0) throw new ArgumentOutOfRangeException(nameof(cols)); _matrix = new T[rows, cols]; }
public int Rows => _matrix.GetLength(0); public int Cols => _matrix.GetLength(1);
// 带有两个参数的索引器,用于单元格访问 public T this[int row, int col] { get => _matrix[row, col]; set => _matrix[row, col] = value; }
// 重载的索引器,用于获取整个行(只读) public T[] this[int row] { get { var rowData = new T[Cols]; for (int i = 0; i < Cols; i++) { rowData[i] = _matrix[row, i]; } return rowData; } }}
public class Program{ public static void Main() { var grid = new Grid<int>(3, 4); grid[1, 2] = 99; // 设置特定单元格
Console.WriteLine($"Value at (1, 2): {grid[1, 2]}");
var secondRow = grid[1]; // 获取整个第二行 Console.WriteLine($"Value from the extracted row at index 2: {secondRow[2]}"); }}常见错误与调试
Section titled “常见错误与调试”IndexOutOfRangeException:这是最常见的错误。当你尝试访问超出底层数据结构有效边界的索引时会发生此错误。始终在你的索引器实现中添加边界检查。KeyNotFoundException:在使用基于字典的索引器时,当你尝试访问不存在的键时会发生此错误。使用TryGetValue来优雅地处理这种情况。Ambiguous Overloads:如果你定义了多个索引器,请确保它们的签名(signature)是唯一的。如果对索引器的调用存在歧义,编译器将报告错误。
索引器是一个强大的功能,可以为你的类创建自定义且直观的 API。为了加深理解,可以考虑探索:
- 在索引器旁实现
IEnumerable<T>等标准接口,以在你的自定义类型上启用 LINQ 查询和foreach循环。 - 在 C# 8.0 的索引和范围(indices and ranges)功能背景下探索索引器,它提供了更简洁的语法来从末尾访问元素或切片集合(如
myCollection[^1])。 - 查阅内置 .NET 集合(如
List<T>和Dictionary<TKey, TValue>)的源代码,了解它们是如何实现的。