Bagikan melalui


Pengindeks

Anda menentukan pengindeks saat instans kelas atau struktur dapat diindeks seperti array atau koleksi lainnya. Nilai terindeks dapat diatur atau diambil tanpa secara eksplisit menentukan jenis atau anggota instans. Pengindeks menyerupai properti kecuali bahwa aksesornya memerlukan parameter.

Contoh berikut mendefinisikan kelas generik dengan get metode aksesor dan set untuk menetapkan dan mengambil nilai.

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

Contoh sebelumnya menunjukkan pengindeks baca/tulis. Ini berisi aksesor get dan set . Anda dapat menentukan pengindeks baca saja sebagai anggota berbadan ekspresi, seperti yang diperlihatkan dalam contoh berikut:

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

}

Kata get kunci tidak digunakan; => memperkenalkan isi ekspresi.

Pengindeks mengaktifkan properti terindeks : properti yang direferensikan menggunakan satu atau beberapa argumen. Argumen tersebut menyediakan indeks ke dalam beberapa kumpulan nilai.

  • Pengindeks memungkinkan objek diindeks mirip dengan array.
  • Aksesor get mengembalikan nilai. Aksesor set menetapkan nilai.
  • Kata this kunci mendefinisikan pengindeks.
  • Kata kunci value menjadi argumen untuk aksesor set.
  • Pengindeks tidak memerlukan nilai indeks bilangan bulat; terserah Anda cara menentukan mekanisme pencarian tertentu.
  • Pengindeks dapat kelebihan beban.
  • Pengindeks dapat memiliki satu atau beberapa parameter formal, misalnya, saat mengakses array dua dimensi.
  • Anda dapat mendeklarasikan pengindeks partial dalam partial jenis.

Anda dapat menerapkan hampir semua yang Anda pelajari dari bekerja dengan properti ke pengindeks. Satu-satunya pengecualian untuk aturan tersebut adalah properti yang diimplementasikan secara otomatis. Pengkompilasi tidak selalu dapat menghasilkan penyimpanan yang benar untuk pengindeks. Anda dapat menentukan beberapa pengindeks pada tipe, dengan daftar argumen yang unik untuk setiap pengindeks.

Penggunaan pengindeks

Anda menentukan pengindeks dalam tipe Anda saat API-nya memodelkan sebuah koleksi. Pengindeks Anda tidak harus memetakan langsung ke tipe koleksi yang merupakan bagian dari kerangka kerja .NET Core. Pengindeks memungkinkan Anda untuk menyediakan API yang cocok dengan abstraksi jenis Anda tanpa mengekspos detail dalam tentang bagaimana nilai untuk abstraksi tersebut disimpan atau dihitung.

Array dan Vektor

Jenis Anda mungkin dapat memodel sebuah array atau vektor. Keuntungan membuat pengindeks Anda sendiri adalah Anda dapat menentukan penyimpanan untuk koleksi tersebut agar sesuai dengan kebutuhan Anda. Bayangkan skenario di mana jenis Anda memodelkan data historis yang terlalu besar untuk dimuat ke dalam memori sekaligus. Anda perlu memuat dan membongkar bagian koleksi berdasarkan penggunaan. Contoh berikut memodelkan perilaku ini. Ini melaporkan berapa banyak titik data yang ada. Ini membuat halaman untuk menyimpan bagian data sesuai permintaan. Ini menghapus halaman dari memori untuk memberi ruang bagi halaman yang diperlukan oleh permintaan yang lebih baru.

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

Anda dapat mengikuti idiom desain ini untuk memodelkan koleksi apa pun di mana ada alasan bagus untuk tidak memuat seluruh kumpulan data ke dalam koleksi dalam memori. Perhatikan bahwa Page kelas adalah kelas berlapis privat yang bukan bagian dari antarmuka publik. Detail tersebut disembunyikan dari pengguna kelas ini.

Kamus-kamus

Skenario umum lainnya adalah ketika Anda perlu memodelkan kamus atau peta. Skenario ini adalah ketika tipe Anda menyimpan nilai berdasarkan kunci, mungkin kunci yang berupa teks. Contoh ini membuat kamus yang menghubungkan argumen baris perintah ke ekspresi lambda yang mengelola opsi tersebut. Contoh berikut menunjukkan dua kelas: ArgsActions kelas yang memetakan opsi baris perintah ke System.Action delegasi, dan ArgsProcessor yang menggunakan ArgsActions untuk mengeksekusi masing-masing Action saat menemukan opsi tersebut.

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

Dalam contoh ini, koleksi ArgsAction selaras dengan koleksi yang mendasar. get menentukan apakah sebuah opsi tertentu telah dikonfigurasi. Jika demikian, mengembalikan Action yang terkait dengan opsi tersebut. Jika tidak, ini mengembalikan yang Action tidak melakukan apa-apa. Akses publik tidak menyertakan pengakses set. Sebaliknya, desainnya menggunakan metode publik untuk mengatur opsi.

Peta Multi-Dimensi

Anda dapat membuat pengindeks yang menggunakan beberapa argumen. Selain itu, argumen tersebut tidak dibatasi untuk menjadi jenis yang sama.

Contoh berikut menunjukkan kelas yang menghasilkan nilai untuk set Mandelbrot. Untuk informasi selengkapnya tentang matematika di balik set, baca artikel ini. Pengindeks menggunakan dua angka desimal ganda untuk menentukan titik pada bidang X, Y. Pengakses get menghitung jumlah iterasi hingga titik ditentukan tidak berada dalam set. Ketika jumlah maksimum iterasi tercapai, titik berada dalam set, dan nilai 'maxIterations' kelas dikembalikan. (Gambar yang dihasilkan komputer yang dipopulerkan untuk set Mandelbrot menentukan warna untuk jumlah iterasi yang diperlukan untuk menentukan apakah suatu titik berada di luar set.)

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 mendefinisikan nilai di setiap koordinat (x,y) untuk nilai bilangan riil. Itu mendefinisikan kamus yang dapat berisi jumlah nilai tak terbatas. Oleh karena itu, tidak ada tempat penyimpanan di belakang peralatan. Sebaliknya, kelas ini menghitung nilai untuk setiap titik ketika kode memanggil get aksesor. Tidak ada penyimpanan dasar yang digunakan.

Rangkuman

Anda membuat pengindeks kapan saja Anda memiliki elemen seperti properti di kelas Anda di mana properti tersebut tidak mewakili satu nilai, melainkan sekumpulan nilai. Satu atau beberapa argumen mengidentifikasi setiap item individual. Argumen tersebut dapat secara unik mengidentifikasi item mana dalam set yang harus dirujuk. Pengindeks memperluas konsep properti, di mana anggota diperlakukan seperti item data dari luar kelas, tetapi seperti metode di dalam. Pengindeks memungkinkan argumen untuk menemukan satu item dalam properti yang mewakili sekumpulan item.

Anda dapat mengakses folder sampel untuk pengindeks. Untuk petunjuk pengunduhan, lihat sampel dan Tutorial.

Spesifikasi Bahasa C#

Untuk informasi selengkapnya, lihat Pengindeks dalam Spesifikasi Bahasa C#. Spesifikasi bahasa adalah sumber definitif untuk sintaks dan penggunaan C#.