索引器 - C#

索引器 - C#

当类或结构的实例可以像数组或其他集合一样编制索引时,可以定义 索引器 。 可以设置或检索索引值,而无需显式指定类型或实例成员。 索引器类似于 属性 ,不同之处在于它们的访问器需要参数。

以下示例定义了一个泛型类,其中包含 get 和 set 访问器方法来分配和检索值。

namespace Indexers;

public class SampleCollection

{

// 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(params IEnumerable 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 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 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 _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 _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 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 访问器时,此类计算每个点的值。 没有使用基础存储。

总结

只要类中有类似于属性的元素,该属性表示的不是单个值,而是一组值,都可以创建索引器。 一个或多个参数标识每个单独的项。 这些参数可以唯一标识应引用的集中的项。 索引器扩展 属性的概念,其中成员被视为类外部的数据项,但类似于内部方法。 索引器允许参数在表示一组项的属性中查找单个项。

可以访问 索引器的示例文件夹。 有关下载说明,请参阅 示例和教程。

C# 语言规范

有关详细信息,请参阅 C# 语言规范中的索引器。 语言规范是 C# 语法和用法的明确来源。

相关数据