c#索引器是一种带参数的特殊属性,允许通过索引像访问数组或字典一样操作对象成员,适用于封装集合或映射数据,提升代码直观性与可读性。
C#的索引器,简单来说,就是一种允许你像访问数组一样,通过索引(比如整数或字符串)来访问对象成员的特殊语法结构。它让你的类表现得像一个集合,但实际上,你可以自定义这个“索引”背后的逻辑,这在处理集合类型数据时特别方便,让代码更直观。
解决方案
索引器(Indexer)在C#中,本质上是一个带有参数的属性(Property)。它允许你为类的实例定义一个默认的索引行为,使得你可以使用方括号
[]
语法来访问对象内部的数据,就像操作数组或列表那样。这对于那些封装了内部集合或需要通过某种键值来访问数据的类来说,是非常有用的。
实现一个索引器,你需要使用
关键字,并指定一个或多个参数类型作为索引。它通常包含
get
和
set
访问器,分别用于读取和写入数据,其工作方式和属性的访问器非常相似。
例如,我们有一个表示书籍集合的类,希望通过书名来获取书的详细信息:
using System; using System.Collections.Generic; public class Book { public String Title { get; set; } public string Author { get; set; } public int Year { get; set; } public Book(string title, string author, int year) { Title = title; Author = author; Year = year; } public override string ToString() { return $"{Title} by {Author} ({Year})"; } } public class Library { private List<Book> _books; public Library() { _books = new List<Book>(); } public void AddBook(Book book) { _books.Add(book); } // 定义一个索引器,通过书名(string)来访问Book对象 public Book this[string title] { get { foreach (var book in _books) { if (book.Title.Equals(title, StringComparison.OrdinalIgnoreCase)) { return book; } } return null; // 如果找不到,返回null } set { // 在这里可以实现更新或添加逻辑 // 简单示例:如果存在,则更新;否则添加 bool found = false; for (int i = 0; i < _books.Count; i++) { if (_books[i].Title.Equals(title, StringComparison.OrdinalIgnoreCase)) { _books[i] = value; // 更新 found = true; break; } } if (!found) { _books.Add(value); // 添加 } } } // 也可以定义一个通过索引(int)来访问的索引器 public Book this[int index] { get { if (index >= 0 && index < _books.Count) { return _books[index]; } throw new IndexOutOfRangeException("索引超出范围。"); } set { if (index >= 0 && index < _books.Count) { _books[index] = value; } else { throw new IndexOutOfRangeException("索引超出范围,无法设置。"); } } } } // 使用示例 public class Program { public static void Main(string[] args) { Library myLibrary = new Library(); myLibrary.AddBook(new Book("The Great Gatsby", "F. Scott Fitzgerald", 1925)); myLibrary.AddBook(new Book("1984", "George Orwell", 1949)); myLibrary.AddBook(new Book("To Kill a Mockingbird", "Harper Lee", 1960)); // 通过字符串索引器访问 Book gatsby = myLibrary["The Great Gatsby"]; Console.WriteLine($"Found: {gatsby}"); // 输出: Found: The Great Gatsby by F. Scott Fitzgerald (1925) // 通过整数索引器访问 Book firstBook = myLibrary[0]; Console.WriteLine($"First book: {firstBook}"); // 输出: First book: The Great Gatsby by F. Scott Fitzgerald (1925) // 使用索引器更新书籍 myLibrary["1984"] = new Book("Nineteen Eighty-Four", "George Orwell", 1949); Console.WriteLine($"Updated: {myLibrary["Nineteen Eighty-Four"]}"); // 输出: Updated: Nineteen Eighty-Four by George Orwell (1949) // 使用索引器添加书籍 (如果set逻辑支持) myLibrary["New Book Title"] = new Book("New Book Title", "New Author", 2023); Console.WriteLine($"Added: {myLibrary["New Book Title"]}"); // 输出: Added: New Book Title by New Author (2023) } }
通过这个例子,你可以看到
Library
类的实例
myLibrary
现在可以像字典一样通过书名来获取
Book
对象,或者像列表一样通过数字索引来获取,这极大地提升了代码的可读性和直观性。
C#索引器与属性(Property)有何不同?何时该用索引器?
这确实是个常见的问题,很多人会觉得索引器和属性很像,都是通过
get/set
访问器来操作数据。但它们之间有个核心区别:索引器是带参数的,而属性不带参数。
一个属性(Property)通常代表了对象的一个命名特性(named characteristic),比如
Book.Title
或
Book.Author
。你通过属性名直接访问它,每次访问都是针对该对象的一个特定、唯一的“值”。
public class Person { public string Name { get; set; } // 属性 public int Age { get; set; } // 属性 }
而索引器则允许你通过一个或多个参数来访问对象内部的集合或映射数据。它让类的实例表现得像一个数组、列表或字典,提供了一种“按位置”或“按键”访问其内部元素的机制。索引器没有显式的名字,而是通过
this
关键字和方括号
[]
来定义和使用。
何时选择使用索引器?
我觉得,当你的类本质上是一个某种元素的集合,或者它内部维护了一个可以通过某种键(不一定是整数)来查找的映射关系时,索引器就非常合适。
- 封装内部集合: 如果你的类内部有一个
List<T>
、
Dictionary<K, V>
或其他集合类型,并且你希望外部代码能够直接通过索引(无论是数字索引还是键值索引)来操作这些内部元素,而不是暴露内部集合本身,那么索引器是理想的选择。这提供了更好的封装性。
- 模拟数组/字典行为: 当你希望你的类实例能够像数组或字典一样被访问时。比如一个
Matrix
类,你可以通过
matrix[row, col]
来访问元素;或者一个
Configuration
类,你可以通过
config["DatabaseConnectionString"]
来获取配置值。
- 提供直观的API: 索引器让代码读起来更自然,更符合我们对集合操作的直觉。
myLibrary["1984"]
比
myLibrary.GetBookByTitle("1984")
看起来更简洁,也更像语言内置的集合操作。
避免在以下情况使用索引器:
- 当你的成员是一个单一、具名的值,而不是集合中的一个元素时,使用属性。
- 当访问逻辑复杂到需要多个参数,且这些参数的组合不构成一个“索引”的概念时,考虑使用方法。
简单来说,如果你的类是“某种东西的集合”,索引器是你的朋友。如果你的类是“有这些特征的对象”,属性是你的朋友。
如何实现一个多参数或只读/只写索引器?有哪些高级用法?
索引器的灵活性远不止于此,你可以根据需求实现更复杂的行为。
多参数索引器:
索引器可以接受一个或多个参数,参数类型也没有限制,可以是
int
,
string
,
Guid
, 甚至是你自定义的类型。这在处理多维数据结构时特别有用,比如矩阵或坐标系统。
public class Matrix { private int[,] _data; private int _rows; private int _cols; public Matrix(int rows, int cols) { _rows = rows; _cols = cols; _data = new int[rows, cols]; } // 多参数索引器,通过行和列访问矩阵元素 public int this[int row, int col] { get { if (row >= 0 && row < _rows && col >= 0 && col < _cols) { return _data[row, col]; } throw new IndexOutOfRangeException("矩阵索引超出范围。"); } set { if (row >= 0 && row < _rows && col >= 0 && col < _cols) { _data[row, col] = value; } else { throw new IndexOutOfRangeException("矩阵索引超出范围,无法设置。"); } } } } // 使用示例 public class MatrixProgram { public static void Main(string[] args) { Matrix m = new Matrix(3, 3); m[0, 0] = 1; m[1, 1] = 5; m[2, 2] = 9; Console.WriteLine($"Matrix element (1,1): {m[1, 1]}"); // 输出: Matrix element (1,1): 5 } }
这里
this[int row, int col]
就是一个多参数索引器,它允许我们像操作二维数组一样操作
Matrix
对象。
只读或只写索引器:
就像属性一样,索引器也可以是只读(只有
get
访问器)或只写(只有
set
访问器)的。
-
只读索引器: 当你只想提供一种通过索引查询数据的方式,而不允许外部修改时。
public class ReadOnlyCollection<T> { private List<T> _items = new List<T>(); public ReadOnlyCollection(IEnumerable<T> items) { _items.AddRange(items); } public T this[int index] { get { if (index >= 0 && index < _items.Count) { return _items[index]; } throw new IndexOutOfRangeException("索引超出范围。"); } // 没有set访问器,所以是只读的 } }
-
只写索引器: 相对少见,但有时当你只想提供一种通过索引设置数据的方式,而不允许直接读取时可能会用到。例如,一个日志系统可能只允许你通过某个键写入日志条目,但不允许直接读取。
public class LogWriter { private Dictionary<string, string> _logs = new Dictionary<string, string>(); public string this[string key] { // 没有get访问器,所以是只写的 set { _logs[key] = value; Console.WriteLine($"Log entry '{key}' set to: {value}"); } } }
索引器重载:
你可以在同一个类中定义多个索引器,只要它们的参数签名(数量和类型)不同即可,这叫做索引器重载。就像
Library
示例中同时存在
this[string title]
和
this[int index]
。这提供了极大的灵活性,让你的类能以多种方式被“索引”。
在接口中定义索引器:
索引器也可以在接口中定义,强制实现该接口的类提供特定的索引行为。
public interface IIndexedCollection<T> { T this[int index] { get; set; } } public class MyList<T> : IIndexedCollection<T> { private List<T> _items = new List<T>(); public T this[int index] { get { return _items[index]; } set { _items[index] = value; } } public void Add(T item) { _items.Add(item); } }
这让设计模式更加强大,能够统一不同集合类的访问方式。
总的来说,索引器是一个非常强大的语言特性,它能让你的自定义类型在处理集合数据时,拥有与内置集合类型相似的直观和简洁性。合理使用它,可以显著提升代码的可读性和维护性。
评论(已关闭)
评论已关闭