Udostępnij za pośrednictwem


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 należy uogólnić 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. Zapobiegnie to niezamierzonemu użyciu Twojej klasy z typami wartości oraz umożliwi użycie operatora as na T elemencie, a także sprawdzenie, czy wartość jest 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 generycznej, szczególnie w przypadku dziedziczenia oraz dostępności członków. 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> { }

Klasy nieogólne, czyli konkretne, mogą dziedziczyć z zamkniętych instancji klas bazowych, ale nie z otwartych klas skonstruowanych lub parametrów typu, ponieważ podczas działania kodu klienta nie istnieje sposób na dostarczenie wymaganego argumentu typu potrzebnego 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 generyczne, które dziedziczą z otwartych skonstruowanych typów, muszą określać ograniczenia, które są nadzbiorem lub implikują ograniczenia typu bazowego.

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>, wystąpi błąd w czasie kompilacji, jeśli spróbujesz podać List<DerivedClass>.

Zobacz też