当类或结构的实例可以像数组或其他集合一样编制索引时,可以定义 索引器 。 可以设置或检索索引值,而无需显式指定类型或实例成员。 索引器类似于 属性 ,不同之处在于它们的访问器需要参数。
以下示例定义了一个泛型类,其中包含 get 和 set 访问器方法来分配和检索值。
namespace Indexers;
public class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
// Define the indexer to allow client code to use [] notation.
public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
}
前面的示例显示了读/写索引器。 它同时包含 get 和 set 访问器。 可以将只读索引器定义为表达式体成员,如以下示例所示。
namespace Indexers;
public class ReadOnlySampleCollection<T>(params IEnumerable<T> items)
{
// Declare an array to store the data elements.
private T[] arr = [.. items];
public T this[int i] => arr[i];
}
get不使用关键字;=>引入表达式正文。
索引器启用 索引 属性:使用一个或多个参数引用的属性。 这些参数为某些值集合提供索引。
- 索引器使对象能够像数组一样编制索引。
-
get取值函数返回值。set取值函数分配值。 - 关键字
this定义索引器。 - 关键字
value用作set访问器的参数。 - 索引器不需要整数索引值;它由你决定如何定义特定的查找机制。
- 索引器可以重载。
- 索引器可以具有一个或多个正式参数,例如,访问二维数组时。
- 可以在
partial类型中声明partial索引器。
你几乎可以将你从处理属性中学到的所有内容应用到索引器中。 该规则的唯一例外是 自动实现属性。 编译器不能始终为索引器生成正确的存储。 只要每个索引器的参数列表是唯一的,就可以在类型上定义多个索引器。
索引器的用法
当类型的 API 为某些集合建模时,可以在类型中定义 索引器 。 索引器不需要直接映射到属于 .NET core 框架的集合类型。 索引器使你能够提供与类型的抽象匹配的 API,而无需公开如何存储或计算该抽象值的内部细节。
数组和向量
你的类型可能会为数组或向量建模。 创建自己的索引器的优点是可以定义该集合的存储以满足你的需求。 假设你的类型对历史数据进行建模,而历史数据太大而无法同时加载到内存中。 根据使用情况,需要加载和卸载数据集合的部分。 以下示例对此行为进行建模。 报告显示存在多少个数据点。 它创建页面以按需保存部分数据。 它会从内存中删除页面,以便为最近请求所需的页面腾出空间。
namespace Indexers;
public record Measurements(double HiTemp, double LoTemp, double AirPressure);
public class DataSamples
{
private class Page
{
private readonly List<Measurements> pageData = new ();
private readonly int _startingIndex;
private readonly int _length;
public Page(int startingIndex, int length)
{
_startingIndex = startingIndex;
_length = length;
// This stays as random stuff:
var generator = new Random();
for (int i = 0; i < length; i++)
{
var m = new Measurements(HiTemp: generator.Next(50, 95),
LoTemp: generator.Next(12, 49),
AirPressure: 28.0 + generator.NextDouble() * 4
);
pageData.Add(m);
}
}
public bool HasItem(int index) =>
((index >= _startingIndex) &&
(index < _startingIndex + _length));
public Measurements this[int index]
{
get
{
LastAccess = DateTime.Now;
return pageData[index - _startingIndex];
}
set
{
pageData[index - _startingIndex] = value;
Dirty = true;
LastAccess = DateTime.Now;
}
}
public bool Dirty { get; private set; } = false;
public DateTime LastAccess { get; set; } = DateTime.Now;
}
private readonly int _totalSize;
private readonly List<Page> pagesInMemory = new ();
public DataSamples(int totalSize)
{
this._totalSize = totalSize;
}
public Measurements this[int index]
{
get
{
if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);
return page[index];
}
set
{
if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);
page[index] = value;
}
}
private Page updateCachedPagesForAccess(int index)
{
foreach (var p in pagesInMemory)
{
if (p.HasItem(index))
{
return p;
}
}
var startingIndex = (index / 1000) * 1000;
var newPage = new Page(startingIndex, 1000);
addPageToCache(newPage);
return newPage;
}
private void addPageToCache(Page p)
{
if (pagesInMemory.Count > 4)
{
// remove oldest non-dirty page:
var oldest = pagesInMemory
.Where(page => !page.Dirty)
.OrderBy(page => page.LastAccess)
.FirstOrDefault();
// Note that this may keep more than 5 pages in memory
// if too much is dirty
if (oldest != null)
pagesInMemory.Remove(oldest);
}
pagesInMemory.Add(p);
}
}
可以遵循此设计成语来为任何类型的集合建模,其中有充分理由不将整个数据集加载到内存中集合中。 请注意,该 Page 类是不属于公共接口的专用嵌套类。 此类的用户无法看到这些详细信息。
字典
另一种常见方案是需要为字典或地图建模。 当类型存储基于键(可能是文本键)的值时出现此情况。 此示例创建一个字典,该字典将命令行参数映射到管理这些选项的 lambda 表达式 。 以下示例演示两个类:一个 ArgsActions 类用于将命令行选项映射到 System.Action 委托,另一个 ArgsProcessor 类在遇到该选项时使用 ArgsActions 执行每个 Action。
namespace Indexers;
public class ArgsProcessor
{
private readonly ArgsActions _actions;
public ArgsProcessor(ArgsActions actions)
{
_actions = actions;
}
public void Process(string[] args)
{
foreach (var arg in args)
{
_actions[arg]?.Invoke();
}
}
}
public class ArgsActions
{
readonly private Dictionary<string, Action> _argsActions = new();
public Action this[string s]
{
get
{
Action? action;
Action defaultAction = () => { };
return _argsActions.TryGetValue(s, out action) ? action : defaultAction;
}
}
public void SetOption(string s, Action a)
{
_argsActions[s] = a;
}
}
在此示例中, ArgsAction 集合将紧密映射到基础集合。
get 确定给定选项是否已配置。 如果是,则返回与该选项关联的 Action。 如果未配置,则返回不执行任何操作的 Action。 公共访问器不包括 set 访问器。 相反,设计是使用公共方法来设置选项。
基于日期的索引器
使用基于日期的数据时,可以使用或DateTimeDateOnly用作索引器键。 仅当需要日期部分并且想要避免与时间相关的复杂情况时使用 DateOnly 。 以下示例显示了用作 DateOnly 主索引器键的温度跟踪系统:
using System;
using System.Collections.Generic;
namespace Indexers;
public class DailyTemperatureData
{
private readonly Dictionary<DateOnly, (double High, double Low)> _temperatureData = new();
// Indexer using DateOnly for date-only scenarios
public (double High, double Low) this[DateOnly date]
{
get
{
if (_temperatureData.TryGetValue(date, out var temp))
{
return temp;
}
throw new KeyNotFoundException($"No temperature data available for {date:yyyy-MM-dd}");
}
set
{
_temperatureData[date] = value;
}
}
// Overload using DateTime for convenience, but only uses the date part
public (double High, double Low) this[DateTime dateTime]
{
get => this[DateOnly.FromDateTime(dateTime)];
set => this[DateOnly.FromDateTime(dateTime)] = value;
}
public bool HasDataFor(DateOnly date) => _temperatureData.ContainsKey(date);
public IEnumerable<DateOnly> AvailableDates => _temperatureData.Keys;
}
此示例演示DateOnly和DateTime两个索引器。 虽然DateOnly索引器是主要接口,但DateTime重载通过仅提取日期部分提供了便利。 此方法可确保一致地处理给定一天的所有温度数据,而不考虑时间组件。
多维地图
可以创建使用多个参数的索引器。 此外,这些参数未限制为相同的类型。
下面的示例演示一个类,该类生成 Mandelbrot 集的值。 有关集背后的数学的详细信息,请阅读 本文。 索引器使用两个双精度型来定义平面 XY 上的一个点。
get 访问器计算迭代次数,直至确定一个点不属于该集为止。 达到最大迭代次数时,该点位于集中,并返回类的 maxIterations 值。 (Mandelbrot 集合常用的计算机生成的图像定义迭代数量的颜色,以便确定一个点是否在集合外部。)
namespace Indexers;
public class Mandelbrot(int maxIterations)
{
public int this[double x, double y]
{
get
{
var iterations = 0;
var x0 = x;
var y0 = y;
while ((x * x + y * y < 4) &&
(iterations < maxIterations))
{
(x, y) = (x * x - y * y + x0, 2 * x * y + y0);
iterations++;
}
return iterations;
}
}
}
Mandelbrot 集合在每个 (x,y) 坐标上为实际数值定义值。 它定义可以包含无限数量的值的字典。 因此,布景后面没有存储空间。 相反,当代码调用 get 访问器时,此类计算每个点的值。 没有使用基础存储。
总结
只要类中有类似于属性的元素,该属性表示的不是单个值,而是一组值,都可以创建索引器。 一个或多个参数标识每个单独的项。 这些参数可以唯一标识应引用的集中的项。 索引器扩展 属性的概念,其中成员被视为类外部的数据项,但类似于内部方法。 索引器允许参数在表示一组项的属性中查找单个项。
可以访问 索引器的示例文件夹。 有关下载说明,请参阅 示例和教程。