Korzystanie z indeksatorów (Przewodnik programowania w języku C#)
Indeksatory to wygoda składniowa, która umożliwia tworzenie klasy, struktury lub interfejsu , do którego aplikacje klienckie mogą uzyskiwać dostęp jako tablicę. Kompilator generuje Item
właściwość (lub alternatywnie nazwaną właściwość, jeśli IndexerNameAttribute jest obecna) i odpowiednie metody dostępu. Indeksatory są najczęściej implementowane w typach, których głównym celem jest hermetyzacja wewnętrznej kolekcji lub tablicy. Załóżmy na przykład, że masz klasę TempRecord
reprezentującą temperaturę w Fahrenheita zarejestrowaną w 10 różnych godzinach w okresie 24-godzinnym. Klasa zawiera tablicę temps
typu float[]
do przechowywania wartości temperatury. Implementując indeksator w tej klasie, klienci mogą uzyskiwać dostęp do temperatur w TempRecord
wystąpieniu jako float temp = tempRecord[4]
zamiast jako float temp = tempRecord.temps[4]
. Notacja indeksatora nie tylko upraszcza składnię aplikacji klienckich; sprawia również, że klasa i jej cel jest bardziej intuicyjny dla innych deweloperów, aby zrozumieć.
Aby zadeklarować indeksator klasy lub struktury, użyj tego słowa kluczowego, jak pokazano w poniższym przykładzie:
// Indexer declaration
public int this[int index]
{
// get and set accessors
}
Ważne
Deklarowanie indeksatora spowoduje automatyczne wygenerowanie właściwości o nazwie Item
w obiekcie. Właściwość Item
nie jest dostępna bezpośrednio z wyrażenia dostępu członka wystąpienia. Ponadto w przypadku dodania własnej Item
właściwości do obiektu za pomocą indeksatora zostanie wyświetlony błąd kompilatora CS0102. Aby uniknąć tego błędu, użyj IndexerNameAttribute nazwy indeksatora zgodnie z opisem w dalszej części tego artykułu.
Uwagi
Typ indeksatora i typ jego parametrów musi być co najmniej tak dostępny, jak sam indeksator. Aby uzyskać więcej informacji na temat poziomów ułatwień dostępu, zobacz Modyfikatory dostępu.
Aby uzyskać więcej informacji na temat używania indeksatorów z interfejsem, zobacz Indeksatory interfejsu.
Podpis indeksatora składa się z liczby i typów jego parametrów formalnych. Nie zawiera typu indeksatora ani nazw parametrów formalnych. Jeśli zadeklarowasz więcej niż jeden indeksator w tej samej klasie, musi mieć różne podpisy.
Indeksator nie jest klasyfikowany jako zmienna; W związku z tym wartość indeksatora nie może być przekazywana przez odwołanie (jako ref
parametr lub out
), chyba że jego wartość jest odwołaniem (czyli zwracane przez odwołanie).
Aby podać indeksatorowi nazwę, która może być używana przez inne języki, użyj polecenia System.Runtime.CompilerServices.IndexerNameAttribute, jak pokazano w poniższym przykładzie:
// Indexer declaration
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index]
{
// get and set accessors
}
Ten indeksator ma nazwę TheItem
, ponieważ jest zastępowany przez atrybut nazwy indeksatora. Domyślnie nazwa indeksatora to Item
.
Przykład 1
W poniższym przykładzie pokazano, jak zadeklarować pole tablicy prywatnej, temps
i indeksator. Indeksator umożliwia bezpośredni dostęp do wystąpienia tempRecord[i]
. Alternatywą dla używania indeksatora jest zadeklarowanie tablicy jako elementu członkowskiego publicznego i bezpośredni dostęp do jego elementów członkowskich. tempRecord.temps[i]
public class TempRecord
{
// Array of temperature values
float[] temps =
[
56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F
];
// To enable client code to validate input
// when accessing your indexer.
public int Length => temps.Length;
// Indexer declaration.
// If index is out of range, the temps array will throw the exception.
public float this[int index]
{
get => temps[index];
set => temps[index] = value;
}
}
Zwróć uwagę, że po ocenie dostępu indeksatora, na przykład w Console.Write
instrukcji, wywoływana jest funkcja get accessor. W związku z tym, jeśli nie get
istnieje akcesorium, wystąpi błąd czasu kompilacji.
var tempRecord = new TempRecord();
// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;
// Use the indexer's get accessor
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"Element #{i} = {tempRecord[i]}");
}
Indeksowanie przy użyciu innych wartości
Język C# nie ogranicza typu parametru indeksatora do liczby całkowitej. Na przykład może być przydatne użycie ciągu z indeksatorem. Taki indeksator może zostać zaimplementowany przez wyszukanie ciągu w kolekcji i zwrócenie odpowiedniej wartości. Ponieważ metody dostępu mogą być przeciążone, wersje ciągów i liczb całkowitych mogą współistnieć.
Przykład 2
Poniższy przykład deklaruje klasę, która przechowuje dni tygodnia. Metoda get
dostępu przyjmuje ciąg, nazwę dnia i zwraca odpowiednią liczbę całkowitą. Na przykład wyrażenie "Niedziela" zwraca wartość 0, "poniedziałek" zwraca wartość 1 itd.
// Using a string as an indexer value
class DayCollection
{
string[] days = ["Sun", "Mon", "Tues", "Wed", "Thurs", "Fri", "Sat"];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[string day] => FindDayIndex(day);
private int FindDayIndex(string day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be in the form \"Sun\", \"Mon\", etc");
}
}
Korzystanie z przykładu 2
var week = new DayCollection();
Console.WriteLine(week["Fri"]);
try
{
Console.WriteLine(week["Made-up day"]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
Przykład 3
Poniższy przykład deklaruje klasę, która przechowuje dni tygodnia przy użyciu wyliczenia System.DayOfWeek . Metoda get
dostępu przyjmuje DayOfWeek
wartość , wartość dnia i zwraca odpowiednią liczbę całkowitą. Na przykład DayOfWeek.Sunday
zwraca wartość 0, DayOfWeek.Monday
zwraca wartość 1 itd.
using Day = System.DayOfWeek;
class DayOfWeekCollection
{
Day[] days =
[
Day.Sunday, Day.Monday, Day.Tuesday, Day.Wednesday,
Day.Thursday, Day.Friday, Day.Saturday
];
// Indexer with only a get accessor with the expression-bodied definition:
public int this[Day day] => FindDayIndex(day);
private int FindDayIndex(Day day)
{
for (int j = 0; j < days.Length; j++)
{
if (days[j] == day)
{
return j;
}
}
throw new ArgumentOutOfRangeException(
nameof(day),
$"Day {day} is not supported.\nDay input must be a defined System.DayOfWeek value.");
}
}
Korzystanie z przykładu 3
var week = new DayOfWeekCollection();
Console.WriteLine(week[DayOfWeek.Friday]);
try
{
Console.WriteLine(week[(DayOfWeek)43]);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine($"Not supported input: {e.Message}");
}
Niezawodne programowanie
Istnieją dwa główne sposoby ulepszania zabezpieczeń i niezawodności indeksatorów:
Pamiętaj, aby uwzględnić jakiś typ strategii obsługi błędów w celu obsługi prawdopodobieństwa przekazania kodu klienta w nieprawidłowej wartości indeksu. W pierwszym przykładzie wcześniej w tym artykule klasa TempRecord udostępnia właściwość Length, która umożliwia kodowi klienta zweryfikowanie danych wejściowych przed przekazaniem go do indeksatora. Można również umieścić kod obsługi błędów wewnątrz samego indeksatora. Pamiętaj, aby udokumentować dla użytkowników wszelkie wyjątki zgłaszane wewnątrz metody dostępu indeksatora.
Ustaw dostępność metod pobierania i ustawiania metod dostępu tak restrykcyjne, jak jest to uzasadnione. Jest to ważne w szczególności dla akcesoriów
set
. Aby uzyskać więcej informacji, zobacz Ograniczanie ułatwień dostępu dostępu.
Zobacz też
- Indexers (Indeksatory)
- Właściwości