Udostępnij za pośrednictwem


Omówienie typów ogólnych

Deweloperzy używają typów ogólnych przez cały czas na platformie .NET, niezależnie od tego, czy niejawnie, czy jawnie. Kiedy używasz linQ na platformie .NET, czy kiedykolwiek zauważysz, że pracujesz z IEnumerable<T>? A jeśli kiedykolwiek widziałeś przykład online "ogólnego repozytorium" do komunikacji z bazami danych przy użyciu platformy Entity Framework, czy widziałeś, że większość metod zwraca IQueryable<T>? Możliwe, że zastanawiałeś się, czym jest T w tych przykładach i dlaczego się tam znajduje.

Po raz pierwszy wprowadzona w programie .NET Framework 2.0 typy ogólne są zasadniczo "szablonem kodu", który umożliwia deweloperom definiowanie bezpiecznych struktur danych bez zatwierdzania rzeczywistego typu danych. Na przykład List<T> to kolekcja ogólna, która może być zadeklarowana i używana z dowolnym typem, takim jak List<int>, List<string>, lub List<Person>.

Aby zrozumieć, dlaczego typy ogólne są przydatne, przyjrzyjmy się konkretnej klasie przed dodaniem typów ogólnych i po dodaniu ich: ArrayList. W programie .NET Framework 1.0 ArrayList elementy były typu Object. Każdy element dodany do kolekcji został dyskretnie przekonwertowany na Object. To samo miało miejsce podczas odczytywania elementów z listy. Ten proces jest znany jako opakowywanie i rozpakowywanie, i ma wpływ na wydajność. Oprócz wydajności nie ma jednak możliwości określenia typu danych na liście w czasie kompilacji, co sprawia, że kod staje się kruchy. Typy ogólne rozwiążą ten problem, definiując typ danych, które będzie zawierać każde wystąpienie listy. Na przykład można dodawać tylko liczby całkowite do List<int> i tylko osoby do List<Person>.

Typy ogólne są również dostępne w czasie wykonywania. Środowisko uruchomieniowe wie, jakiego typu struktury danych używasz, i może przechowywać je w pamięci wydajniej.

Poniższy przykład to mały program, który ilustruje wydajność znajomości typu struktury danych w czasie wykonywania:

  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.Diagnostics;

  namespace GenericsExample {
    class Program {
      static void Main(string[] args) {
        //generic list
        List<int> ListGeneric = new List<int> { 5, 9, 1, 4 };
        //non-generic list
        ArrayList ListNonGeneric = new ArrayList { 5, 9, 1, 4 };
        // timer for generic list sort
        Stopwatch s = Stopwatch.StartNew();
        ListGeneric.Sort();
        s.Stop();
        Console.WriteLine($"Generic Sort: {ListGeneric}  \n Time taken: {s.Elapsed.TotalMilliseconds}ms");

        //timer for non-generic list sort
        Stopwatch s2 = Stopwatch.StartNew();
        ListNonGeneric.Sort();
        s2.Stop();
        Console.WriteLine($"Non-Generic Sort: {ListNonGeneric}  \n Time taken: {s2.Elapsed.TotalMilliseconds}ms");
        Console.ReadLine();
      }
    }
  }

Ten program generuje dane wyjściowe podobne do następujących:

Generic Sort: System.Collections.Generic.List`1[System.Int32]
 Time taken: 0.0034ms
Non-Generic Sort: System.Collections.ArrayList
 Time taken: 0.2592ms

Pierwszą rzeczą, którą można zauważyć, jest to, że sortowanie listy ogólnej jest znacznie szybsze niż sortowanie listy nieogólne. Można również zauważyć, że typ listy ogólnej jest odrębny ([System.Int32]), podczas gdy typ listy nieogółowej jest uogólniony. Ponieważ środowisko uruchomieniowe wie, że ogólny List<int> jest typu Int32, może przechowywać elementy listy w podstawowej tablicy całkowitej w pamięci, podczas gdy niegeneryczny ArrayList musi rzutować każdy element listy na obiekt. Jak pokazano w tym przykładzie, dodatkowe rzuty zajmują trochę czasu i spowalniają sortowanie listy.

Dodatkową zaletą znajomości przez środowisko uruchomieniowe typu ogólnego jest lepsze doświadczenie debugowania. Podczas debugowania ogólnego w języku C#wiesz, jaki typ każdego elementu znajduje się w strukturze danych. Bez typów ogólnych nie byłoby pojęcia, jaki był typ każdego elementu.

Zobacz także