當類別或結構實例可以像數位或其他集合一樣編製索引時,您可以定義 索引器 。 您可以設定或擷取索引值,而不需明確指定類型或實例成員。 索引器類似於 屬性 ,不同之處在於其存取子會採用參數。
下列範例定義了一個具備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
。
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 集合值的類別。 如需集合背後的數學詳細資訊,請閱讀 這篇文章。 索引器會使用兩個雙精度浮點數來定義 X、Y 平面上的點。
get
存取器會計算迭代次數,直到某個點被確定超出集合範圍為止。 到達最大反覆運算次數時,該點位於集合內,並會傳回類別的最大反覆運算次值。 計算機生成並普及化的 Mandlebrot 集合影像定義了判斷某個點在集合外所需反覆次數的色彩。
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 Set 會針對實數值在每個 (x,y) 座標上定義值。 定義可包含無限數目值的字典。 因此,後面沒有存放空間。 相反地,當程式代碼呼叫 get
存取子時,這個類別會計算每個點的值。 沒有使用任何基礎儲存。
總結
您可以在類別中擁有類似屬性的元素時建立索引器,該屬性不代表單一值,而是一組值。 一或多個參數會識別每個個別項目。 這些引數可以唯一識別應該參考集合中的項目。 索引器擴充 屬性的概念,其中成員會被視為來自 類別外部的數據項,但就像內部的方法一樣。 索引器允許引數在屬性中尋找單一項目,這個屬性代表一組項目。
您可以存取 索引器的範例資料夾。 如需下載指示,請參閱 範例和教學課程。