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 zobaczysz przykład online "ogólnego repozytorium" do rozmowy z bazami danych przy użyciu platformy Entity Framework, czy zobaczysz, że większość metod zwraca IQueryable<T>
? Być może zastanawiałeś się, co T znajduje się w tych przykładach i dlaczego jest tam.
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 to kolekcja ogólna, List<T> 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
element . To samo miało miejsce podczas odczytywania elementów z listy. Ten proces jest znany jako boks 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 w przypadku niektórych kruchych kodu nie ma możliwości. 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 i List<int>
dodawać tylko osoby do elementu 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ą środowiska uruchomieniowego wiedząc o typie ogólnym jest lepsze środowisko 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.