当类或结构的实例可以像数组或其他集合一样编制索引时,可以定义 索引器 。 可以设置或检索索引值,而无需显式指定类型或实例成员。 索引器类似于 属性 ,不同之处在于它们的访问器需要参数。
以下示例定义了一个泛型类,其中包含 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
访问器。 相反,设计是使用公共方法来设置选项。
多维地图
可以创建使用多个参数的索引器。 此外,这些参数未限制为相同的类型。
下面的示例演示一个类,该类生成 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
访问器时,此类计算每个点的值。 没有使用基础存储。
总结
只要类中有类似于属性的元素,该属性表示的不是单个值,而是一组值,都可以创建索引器。 一个或多个参数标识每个单独的项。 这些参数可以唯一标识应引用的集中的项。 索引器扩展 属性的概念,其中成员被视为类外部的数据项,但类似于内部方法。 索引器允许参数在表示一组项的属性中查找单个项。
可以访问 索引器的示例文件夹。 有关下载说明,请参阅 示例和教程。