Klasy ogólne (Przewodnik programowania w języku C#)
Klasy ogólne hermetyzują operacje, które nie są specyficzne dla określonego typu danych. Najczęstszym zastosowaniem dla klas ogólnych są kolekcje, takie jak listy połączone, tabele skrótów, stosy, kolejki, drzewa itd. Operacje, takie jak dodawanie i usuwanie elementów z kolekcji, są wykonywane w taki sam sposób, niezależnie od typu przechowywanych danych.
W przypadku większości scenariuszy wymagających klas kolekcji zalecane jest użycie tych dostępnych w bibliotece klas platformy .NET. Aby uzyskać więcej informacji na temat używania tych klas, zobacz Ogólne kolekcje na platformie .NET.
Zazwyczaj klasy ogólne są tworzone, zaczynając od istniejącej klasy, i zmieniając typy na parametry typu pojedynczo, dopóki nie osiągniesz optymalnej równowagi uogólniania i użyteczności. Podczas tworzenia własnych klas ogólnych ważne zagadnienia obejmują następujące kwestie:
Typy, które mają być uogólniane do parametrów typu.
Z reguły tym więcej typów można sparametryzować, tym bardziej elastyczny i wielokrotnego użytku staje się kod. Jednak zbyt wiele uogólniania może tworzyć kod, który jest trudny dla innych deweloperów do odczytania lub zrozumienia.
Jakie ograniczenia, jeśli istnieją, mają zastosowanie do parametrów typu (zobacz Ograniczenia dotyczące parametrów typu).
Dobrą regułą jest zastosowanie maksymalnych ograniczeń, które nadal umożliwiają obsługę typów, które należy obsłużyć. Jeśli na przykład wiesz, że klasa ogólna jest przeznaczona tylko do użytku z typami referencyjnymi, zastosuj ograniczenie klasy. Uniemożliwi to niezamierzone użycie klasy z typami wartości i umożliwi użycie
as
operatora wT
elemecie i sprawdzenie wartości null.Czy uwzględniać ogólne zachowanie w klasach bazowych i podklasach.
Ponieważ klasy ogólne mogą służyć jako klasy bazowe, te same zagadnienia projektowe mają zastosowanie tutaj, jak w przypadku klas innych niż ogólne. Zobacz reguły dziedziczenia z ogólnych klas bazowych w dalszej części tego tematu.
Czy zaimplementować co najmniej jeden interfejs ogólny.
Jeśli na przykład projektujesz klasę, która będzie używana do tworzenia elementów w kolekcji opartej na rodzajach, może być konieczne zaimplementowanie interfejsu, takiego jak IComparable<T> gdzie
T
jest typem klasy.
Aby zapoznać się z przykładem prostej klasy ogólnej, zobacz Wprowadzenie do typów ogólnych.
Reguły dotyczące parametrów typu i ograniczeń mają kilka konsekwencji dla zachowania klasy ogólnej, szczególnie w przypadku dziedziczenia i ułatwień dostępu składowych. Przed kontynuowaniem należy zrozumieć niektóre terminy. W przypadku klasy Node<T>
ogólnej kod klienta może odwoływać się do klasy przez określenie argumentu typu — w celu utworzenia zamkniętego typu skonstruowanego (Node<int>
) lub pozostawienia nieokreślonego parametru typu — na przykład w przypadku określenia ogólnej klasy bazowej w celu utworzenia otwartego typu skonstruowanego (Node<T>
). Klasy ogólne mogą dziedziczyć z betonu, zamkniętej konstrukcji lub otwartych skonstruowanych klas bazowych:
class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
class NodeConcrete<T> : BaseNode { }
//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }
//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }
Innymi słowy, klasy nieogólne mogą dziedziczyć z zamkniętych skonstruowanych klas bazowych, ale nie z otwartych skonstruowanych klas lub parametrów typu, ponieważ nie ma możliwości w czasie wykonywania kodu klienta w celu dostarczenia argumentu typu wymaganego do utworzenia wystąpienia klasy bazowej.
//No error
class Node1 : BaseNodeGeneric<int> { }
//Generates an error
//class Node2 : BaseNodeGeneric<T> {}
//Generates an error
//class Node3 : T {}
Klasy ogólne dziedziczone z otwartych typów skonstruowanych muszą dostarczać argumenty typu dla dowolnych parametrów typu klasy bazowej, które nie są współużytkowane przez klasę dziedziczącą, jak pokazano w poniższym kodzie:
class BaseNodeMultiple<T, U> { }
//No error
class Node4<T> : BaseNodeMultiple<T, int> { }
//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }
//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}
Klasy ogólne dziedziczone z otwartych typów skonstruowanych muszą określać ograniczenia, które są nadzbiorem lub implikują ograniczenia typu podstawowego:
class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }
Typy ogólne mogą używać wielu parametrów i ograniczeń typu w następujący sposób:
class SuperKeyType<K, V, U>
where U : System.IComparable<U>
where V : new()
{ }
Typy konstrukcji otwartych i zamkniętych mogą być używane jako parametry metody:
void Swap<T>(List<T> list1, List<T> list2)
{
//code to swap items
}
void Swap(List<int> list1, List<int> list2)
{
//code to swap items
}
Jeśli klasa ogólna implementuje interfejs, wszystkie wystąpienia tej klasy można rzutować do tego interfejsu.
Klasy ogólne są niezmienne. Innymi słowy, jeśli parametr wejściowy określa List<BaseClass>
parametr , zostanie wyświetlony błąd czasu kompilacji, jeśli spróbujesz podać List<DerivedClass>
element .