Przejdź do głównej zawartości

[C#] Czym są Tuple i jak ich używać?


Tuple jest strukturą danych, która pozwala agregować w jednym obiekcie kilka elementów o dowolnych typach. Zobrazujmy to przykładem:

var parameters = new Tuple<int, string, DateTime>(2, "siemano", new DateTime(2000, 1, 1));

Innym sposobem na stworzenie obiektu klasy Tuple jest użycie metody Tuple.Create:

            var parameters = Tuple.Create(2, "siemano", new DateTime(2000, 1, 1));

W przykładzie widzimy Tuple, który przechowuje w sobie trzy elementy - inta, stringa i obiekt daty. Możemy się do nich odwołać w ten sposób:

            var number = parameters.Item1;
            var text = parameters.Item2;
            var date = parameters.Item3;

Dostęp do poszczególnych elementów Tuple jest więc bardzo mało elegancki i nic nie mówi nam o prawdziwym przeznaczeniu pól Tuple. Nie wiemy, czy pierwszy element - liczba -  jest ilością czegoś, czyimś wiekiem czy może numerem strony w książce. Jest to poważna wada Tuple.

Tuple ograniczony jest do ośmiu elementów. Próba utworzenia Tuple z większą ilością pól zakończy się błędem kompilacji. Jest to kolejny spory problem z Tuple:

            var parameters = Tuple.Create(1, 2, 3, 4, 5, 6, 7, 8, 9); //błąd kompilacji

Tuple mają więc poważne wady:
  • poszczególne elementy nazywają się Item1, Item2, co absolutnie nic nie mówi czytającemu
  • możemy mieć maksymalnie 8 elementów w Tuple (możemy oczywiście mieć Tuple zagnieżdżone. Moim zdaniem podniesie to wady Tuple do potęgi)
  • Tuple nie posiada sensownej nazwy. Agregując w nim na przykład dane osoby takie jak wiek, imię i adres, tylko z kontekstu możemy domyślić się że Tuple reprezentuje osobę. Gdybyśmy użyli klasy lub struktury, ich nazwa wyjaśniałaby wszystko
  • Tuple, w przeciwieństwie do struktury czy klasy, jest typem dość prymitywnym. Nie możemy go rozszerzać o metody, stosować dziedziczenia (jak w klasach) czy implementować interfejsów (jak zarówno w klasach, jak i strukturach)
Możemy się więc zastanowić, kiedy używać Tuple. Przytoczę kilka powszechnych w sieci przykładów i odniosę się do ich sensowności.
1) Tupli możemy użyć, gdy chcemy zwrócić z metody więcej niż jedną wartość
Moim zdaniem prawie nigdy nie powinniśmy tego robić. Jeśli dane, które chcemy zwrócić z funkcji nie mieszczą się w jednej wartości, powinniśmy rozważyć utworzenie sensownie nazwanej klasy lub struktury, która je w sobie zagreguje. W tej strukturze danych będziemy mieć pola o jasno brzmiących nazwach, a nie z tajemniczych "Item1", czy "Item2". Alternatywą do użycia Tuple w takiej sytuacji jest użycie parametru z modyfikatorem out (tutaj możesz poczytać o nich więcej). Popularnym przykładem jest metoda int.TryParse, która zwraca informację o tym czy parsowanie się powiodło, a jego wynik zwraca dodatkowo przez parametr z modyfikatorem out:

            int parsedNumber;
            var didParseSuccessfully = int.TryParse("11", out parsedNumber);

Moim zdaniem zarówno Tuple, jak i out są dość brzydkimi rozwiązaniami i powinny być używane w ostateczności. W przypadku funkcji TryParse uważam, że out jest mniejszym złem - pozwala nam bowiem uniknąć koszmarnych "Item1", "Item2".
2) Tupli możemy użyć, gdy potrzebujemy przekazać kilka zmiennych do funkcji używając jednego parametru
Moim zdaniem jest to jeszcze gorszy pomysł, niż powyższy. Nic bowiem nie stoi na przeszkodzie, by do funkcji przekazać tyle parametrów, na ile mamy ochotę. Jeśli parametry te łączą się w spójną całość, powinniśmy rozważyć utworzenie odpowiedniej struktury danych.

Zamiast takiego kodu:  

        public void PrintPersonData(string name, string lastname, int age)
        {
            Console.WriteLine($"Imię: {name}, nazwisko: {lastname}, wiek: {age}");
        }

Powinniśmy napisać:

    struct Person
    {
        public string Name { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
    }
    class Program
    {
        public void PrintPersonData(Person person)
        {
            Console.WriteLine($"Imię: {person.Name}, nazwisko: {person.LastName}, wiek: {person.Age}");
        }
    }

Podsumowując:
Kiedy używać Tuple? Moim zdaniem najlepiej ich unikać. Tuple są mało czytelne i nie komunikują jasno co właściwie w sobie przechowują.

Ich użycie mogłoby być zasadne w takich przypadkach jak przechowywanie odczytanych rekordów z bazy, dla których nie chcemy tworzyć osobnej struktury danych w naszej aplikacji - ponieważ chcemy je przeorganizować, obciąć czy rozszerzyć, zanim zapiszemy je w instancjach klas czy struktur. Wtedy Tuple może posłużyć jako rodzaj tymczasowej struktury danych. Jeśli decydujemy się na użycie Tuple, miejmy na uwadze jak mało mówiąca jest to struktura i zastanówmy się, czy sami chcielibyśmy się domyślać, co reprezentuje Item1.

Od C# 7 dostępna jest nowa klasa: ValueTuple. Więcej o niej można przeczytać tutaj [under construction].

Komentarze