共用方式為


索引員

當類別或結構實例可以像數位或其他集合一樣編製索引時,您可以定義 索引器 。 您可以設定或擷取索引值,而不需明確指定類型或實例成員。 索引器類似於 屬性 ,不同之處在於其存取子會採用參數。

下列範例定義了一個具備getset存取子方法的泛型類別,用於指派和擷取值。

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;
   }
}

上述範例顯示讀取/寫入索引器。 它同時包含 getset 存取子。 您可以將唯讀索引器定義為表示式主體成員,如下列範例所示:

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 存取子時,這個類別會計算每個點的值。 沒有使用任何基礎儲存。

總結

您可以在類別中擁有類似屬性的元素時建立索引器,該屬性不代表單一值,而是一組值。 一或多個參數會識別每個個別項目。 這些引數可以唯一識別應該參考集合中的項目。 索引器擴充 屬性的概念,其中成員會被視為來自 類別外部的數據項,但就像內部的方法一樣。 索引器允許引數在屬性中尋找單一項目,這個屬性代表一組項目。

您可以存取 索引器的範例資料夾。 如需下載指示,請參閱 範例和教學課程

C# 語言規格

如需詳細資訊,請參閱 C# 語言規格中的索引器。 語言規格是 C# 語法和使用方式的最終來源。