sobota, 22 sierpnia 2020

Nowe operatory indeksu i zakresu dla tablic w C# 8.0

C# 8.0 wniósł dwie nowości dla tablic jak i wszystkiego co implementuje interfejs IList<T>. Te dwie nowości to Index i Range:


Pierwszą z nowości jest Index - jak sama nazwa wskazuje jest to pozycja elementu w tablicy. Index to typ wartościowy - struktura readonly. Tworzyć obiekt tego typu możemy jak dla każdej innej struktury:


            int[] tab = { 1, 2, 3, 4, 5, 6 };

            var index0 = new Index(1, fromEnd: true);
            Console.WriteLine(tab[index0]);


Dodatkowy parametr (opcjonalny) fromEnd pozwala zdefiniować czy szukamy elementu o indeksie 1 od początku kolekcji czy od końca. W tym przypadku otrzymamy 1 wartość od  końca czyli 6. Index nie musi być "ręcznie" tworzony w kodzie - możemy posłużyć się operatorem ^ i tak aby pobrać ostatni element wystarczy zapis:


            int[] tab = { 1, 2, 3, 4, 5, 6 };

            Console.WriteLine(tab[^1]);


Ktoś może zapytać: "zaraz, skoro tablice są numerowane od 0, to dlaczego ostatni element pobieramy za pomocą ^1 a nie ^0"?

Sprawa jest dosyć prosta patrząc na poniższy rysunek, przedstawiający 4 elementową tablicę:


Kiedy chcemy pobrać element kolekcji (np. tablicy) poprzez indeks - żądamy pobrania elementu który zaczyna się na danej pozycji. Czyli dla przykładu jeżeli chcemy pobrać element na pozycji 0 to znaczy że chcemy odczytać wartość zawartą między indeksami 0 i 1. Dlatego własnie, patrząc na rysunek jeżeli chcielibyśmy pobrać wartość elementu ^0 oznaczałoby to chęć pobrania elementu tablicy który jest za ostatnim elementem tablicy, czyli chcemy odczytać pamięć która już nie należy do bloku tablicy. Próba taka zwróci oczywiście błąd. W drugą stronę działa to tak samo - jeżeli chcielibyśmy odczytać element na pozycji 4. Ponieważ tablica ma tylko 4 elementu, nie ma ona wartości dla indeksu między 4 i 5. 


Drugą nowością jest operator zakresu. Zakres to nic innego jak dwa indeksy: początek i koniec. Najłatwiej będzie zaprezentować ten operator na przykładach:


        static void Main(string[] args)
        {
            int[] tab = { 1, 2, 3, 4, 5, 6 };
            var range1 = new Range(2, 3);
            var range2 = 2..3;
            var range3 = 1..^2;
            var range4 = Range.All;
            var range5 = Range.EndAt(2);
            var range6 = 0..^1;
            var range7 = ..;
            var range8 = 2..;
            var range9 = ..2;
            PrintContent(tab, range1, nameof(range1));
            PrintContent(tab, range2, nameof(range2));
            PrintContent(tab, range3, nameof(range3));
            PrintContent(tab, range4, nameof(range4));
            PrintContent(tab, range5, nameof(range5));
            PrintContent(tab, range6, nameof(range6));
            PrintContent(tab, range7, nameof(range7));
            PrintContent(tab, range8, nameof(range8));
            PrintContent(tab, range9, nameof(range9));
        }

        private static void PrintContent(int[] tab, Range range, string rangeName)
        {
            Console.WriteLine(rangeName);
            Console.WriteLine(string.Join(", ", tab[range]));
            Console.WriteLine(new string('-', 40));
        }

Po zapoznaniu się z częścią poświęconą indeksom bardzo łatwo zrozumieć operator zakresu. Warto zapamiętać domyślne wartości:
  • jeżeli nie podamy lewego zakresu (startowego) będzie to zawsze 0
  • jeżeli nie podamy prawego zakresu (końcowego) będzie to zawsze -1
  • jeżeli stworzymy pusty obiekt zakresu (new Range()) zostanie stworzony zakres 0..0, czyli nie pobierzemy żadnego elementu. Jest to o tyle istotne, że domyślnie dla zakresu ".." dostajemy całą tablicę.
W jaki sposób działa Range? Działanie jest proste - zostaje stworzona nowa tablica i przekopiowane do niej wartości z zadanego zakresu. Zakres działa także dla typu string. Stosując go na obiektach łańcuchowych otrzymujemy sub-string. 

Brak komentarzy:

Prześlij komentarz