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:

  1. Jeśli typ posiada własność Length bądź Count (taki typ zwany jest Countable) to zapis array[^a] będzie traktowany jako array[array.Length - a] (bądź odpowiednio z Count).
  2. Jeśli typ posiada metodę Slice to zapis array[a..b] będzie zastąpiony poprzez array.Slice(a, b-a).
  3. 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.

Kategorie: C#

0 komentarzy

Dodaj komentarz

Avatar placeholder

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *