Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Indeksatory definiuje się, gdy wystąpienia klasy lub struktury mogą być indeksowane jak tablica lub inna kolekcja. Wartość indeksowana może być ustawiona lub pobrana bez jawnego określania typu lub członka instancji. Indeksatory przypominają właściwości, z tą różnicą, że ich akcesory przyjmują parametry.
W poniższym przykładzie zdefiniowano klasę ogólną z metodami dostępu get
i set
w celu przypisywania i pobierania wartości.
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;
}
}
W poprzednim przykładzie pokazano indeksator odczytu/zapisu. Zawiera zarówno get
jak i set
akcesory. Indeksatory tylko do odczytu można zdefiniować jako element członkowski wyrażeń, jak pokazano w poniższych przykładach:
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];
}
Słowo get
kluczowe nie jest używane; =>
wprowadza treść wyrażenia.
Indeksatory włączają właściwości indeksowane : właściwości przywoływane przy użyciu co najmniej jednego argumentu. Te argumenty udostępniają indeks do kolekcji wartości.
- Indeksatory umożliwiają indeksowanie obiektów podobnych do tablic.
- Akcesor
get
zwraca wartość. Akcesoriumset
przypisuje wartość. - Słowo
this
kluczowe definiuje indeksator. - Kluczowe słowo
value
jest argumentem akcesoraset
. - Indeksatory nie wymagają wartości indeksu całkowitego; Do ciebie należy zdefiniowanie określonego mechanizmu wyszukiwania.
- Indeksatory mogą być przeciążone.
- Indeksatory mogą mieć co najmniej jeden parametr formalny, na przykład podczas uzyskiwania dostępu do tablicy dwuwymiarowej.
- Indeksatory można zadeklarować
partial
wpartial
typach.
Możesz zastosować prawie wszystko, czego nauczyłeś się podczas pracy z właściwościami do indeksatorów. Jedynym wyjątkiem od tej reguły jest automatycznie zaimplementowane właściwości. Kompilator nie zawsze może wygenerować prawidłowy magazyn dla indeksatora. Można zdefiniować wiele indeksatorów w typie, o ile listy argumentów dla każdego indeksatora są unikatowe.
Użycie indeksatorów
Indeksatory są definiowane w typie, gdy jego interfejs API modeluje jakąś kolekcję. Twój indeksator nie musi mapować bezpośrednio na typy kolekcji, które są częścią platformy .NET Core. Indeksatory umożliwiają podanie interfejsu API zgodnego z abstrakcją typu bez uwidaczniania wewnętrznych szczegółów sposobu przechowywania lub obliczania wartości dla tej abstrakcji.
Tablice i wektory
Twój typ może modelować tablicę lub wektor. Zaletą tworzenia własnego indeksatora jest możliwość zdefiniowania magazynu dla tej kolekcji zgodnie z potrzebami. Wyobraź sobie scenariusz, w którym typ modeluje dane historyczne, które są zbyt duże, aby załadować je do pamięci jednocześnie. Musisz załadować i rozładować sekcje kolekcji w zależności od ich użycia. W poniższym przykładzie przedstawiono modele tego zachowania. Raportuje, ile punktów danych istnieje. Tworzy strony do przechowywania sekcji danych na żądanie. Usuwa strony z pamięci, aby zapewnić miejsce na strony wymagane przez nowsze żądania.
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);
}
}
Możesz postępować zgodnie z tym idiomem projektowym, aby modelować dowolny rodzaj kolekcji, gdzie istnieją dobre powody, aby nie wczytywać całego zestawu danych do kolekcji w pamięci operacyjnej. Zwróć uwagę, że Page
to prywatna klasa zagnieżdżona, która nie jest częścią interfejsu publicznego. Te szczegóły są ukryte przed użytkownikami tej klasy.
Słowniki
Innym typowym scenariuszem jest modelowanie słownika lub mapy. Ten scenariusz odnosi się do sytuacji, w której typ przechowuje wartości według klucza, które mogą być kluczami tekstowymi. W tym przykładzie tworzony jest słownik, który przypisuje argumenty wiersza polecenia do wyrażeń lambda, które obsługują te opcje. W poniższym przykładzie pokazano dwie klasy: klasę ArgsActions
, która mapuje opcję wiersza polecenia na System.Action delegata, oraz klasę ArgsProcessor
, która używa ArgsActions
do wykonania każdej Action w przypadku napotkania tej opcji.
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;
}
}
W tym przykładzie kolekcja ArgsAction
ściśle odwzorowuje podstawową kolekcję. Określa get
, czy dana opcja jest skonfigurowana. Jeśli tak, zwraca Action skojarzone z tą opcją. Jeśli tak nie jest, zwraca wartość Action , która nic nie robi. Dostęp publiczny nie zawiera set
metody dostępu. Projekt używa raczej metody publicznej do ustawiania opcji.
Mapy wielowymiarowe
Można utworzyć indeksatory używające wielu argumentów. Ponadto argumenty te nie są ograniczone do tego samego typu.
W poniższym przykładzie przedstawiono klasę, która generuje wartości dla zestawu Mandelbrot. Aby uzyskać więcej informacji na temat matematyki za zestawem, przeczytaj ten artykuł. Indeksator używa dwóch podwójnych do zdefiniowania punktu w płaszczyźnie X, Y. Akcesor get
oblicza liczbę iteracji, aż punkt zostanie uznany za nieprzynależący do zbioru. Po osiągnięciu maksymalnej liczby iteracji punkt znajduje się w zestawie, a wartość maxIterations klasy jest zwracana. Obrazy wygenerowane komputerowo, spopularyzowane przez zbiór Mandelbrota, definiują kolory w zależności od liczby iteracji niezbędnych do określenia, że punkt znajduje się poza zbiorem.
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;
}
}
}
Zestaw Mandelbrot definiuje wartości na każdej współrzędnej (x,y) dla wartości liczb rzeczywistych. Definiuje słownik, który może zawierać nieskończoną liczbę wartości. W związku z tym nie ma miejsca za zestawem. Zamiast tego klasa oblicza wartość dla każdego punktu, gdy kod wywołuje akcesor get
. Nie ma korzystania z żadnego podstawowego magazynu danych.
Sumowanie
Indeksatory są tworzone w dowolnym momencie, gdy w klasie znajduje się element podobny do właściwości, w którym ta właściwość nie reprezentuje jednej wartości, ale raczej zestaw wartości. Co najmniej jeden argument identyfikuje każdy pojedynczy element. Te argumenty mogą jednoznacznie zidentyfikować element w zestawie, do którego należy się odwoływać. Indeksatory rozszerzają koncepcję właściwości, gdzie członek klasy jest traktowany jak zewnętrzny element danych, ale wewnętrznie jak metoda. Indeksatory umożliwiają argumentom znalezienie pojedynczego elementu we właściwości reprezentującej zestaw elementów.
Możesz uzyskać dostęp do przykładowego folderu dla indeksatorów. Aby uzyskać instrukcje dotyczące pobierania, zobacz Przykłady i samouczki.
Specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz Indeksatory w specyfikacji języka C#. Specyfikacja języka jest ostatecznym źródłem informacji o składni i użyciu języka C#.