Niedawno przeglądając kod jednego z projektów na GitHubie natknąłem się na dziwną, nieznaną mi wtedy składnię w dostępie przez indekser. Okazało się, że owa składnia to mała nowość z C# 8.0. Chciałbym ją dziś zaprezentować.
Indeksy
Składnia dostępu pod konkretny indeks w tablicy jest bardzo dobrze znana.
var array = new [] {10, 20, 30, 40, 50};
Console.WriteLine(array[2]);
Jeśli jednak chcielibyśmy odwołać się do przedostatniego elementu tablicy, moglibyśmy zrobić to w następujący, lekko nieczytelny, sposób:
array[array.Length-2]
Dzięki nowej składni, możemy to jednak zrobić inaczej:
array[^2]
Wystarczy poprzedzić liczbę znakiem ^
, a indeks zostanie policzony od tyłu. Tak naprawdę zapis ^a
jest równoznaczny z
new System.Index(a, fromEnd: true)
. Następnie obiekt typu System.Index
jest przekazywany do indeksera. Pozwala to na własną obsługę nowej składni. Poniżej krótki przykład:
public class IndexIndexerClass
{
public int this[System.Index index]
{
get => index.IsFromEnd ? -index.Value : index.Value;
}
}
Wszystkie dostępne własności i metody System.Index
możemy sprawdzić w dokumentacji.
Zakresy
Oprócz dostępu do elementów licząc od końca, nowa wersja języka została wzbogacona o możliwość odwoływania się do zakresów. Wygląda to tak:
array[1..3];
Jako wynik otrzymamy wszystkie elementy poczynając od pierwszego, a kończąc na trzecim (bez trzeciego). Możliwe jest także połączenie zakresów i indeksów:
array[1..^1]
Powyższe wyrażenie zwróci tablicę składającą się z wszystkich elementów oryginalnej tablicy oprócz pierwszego i ostatniego.
Również i w przypadku zakresów wyrażenie a..b
zamieniane jest na odpowiadający obiekt: new System.Range(a, b)
. Aby klasa mogła korzystać z zakresów, wystarczy zaimplementować indekser przyjmujący System.Range
.
public class RangeIndexerClass
{
public int this[System.Range range]
{
get => range.GetOffsetAndLength(100).Length;
}
}
Dostępne metody i własności System.Range
można sprawdzić w dokumentacji.
Niejawna implementacja indeksów i zakresów
Najczęściej implementacja indekserów dla indeksów i zakresów będzie taka sama. Aby nie wymagać od programistów nadmiarowej pracy, przyjęte zostały następujące zasady:
- Jeśli typ posiada własność
Length
bądźCount
(taki typ zwany jest Countable) to zapisarray[^a]
będzie traktowany jakoarray[array.Length - a]
(bądź odpowiednio z Count). - Jeśli typ posiada metodę
Slice
to zapisarray[a..b]
będzie zastąpiony poprzezarray.Slice(a, b-a)
. - Powyższe zasady mogą być ze sobą łączone.
Przykład podstawień wg powyższych reguł w SharpLab.io.
Bardziej formalny opis zasad podstawień znajduje się na MSDN.
Ciekawostka
Może nie do końca oczywste na pierwszy rzut oka, ale poniższe wyrażenia są jak najbardziej poprawne.
var index = ^1;
var range = 1..^2;
Podsumowanie
Przedstawiony w tym poście syntactic sugar wydaje mi się całkiem przydatnym udogodnieniem. Pokazuje także, że zespół pracujący nad rozwojem języka C# chce upraszczać życie programistom tegoż języka. Osobiście bardzo mnie to cieszy.
Na koniec dodam jeszcze, że przykłady z tego postu znajdują się także na moim GitHubie.
0 komentarzy