Универсальные классы (руководство по программированию на C#)

Обновлен: Ноябрь 2007

Универсальные классы инкапсулируют операции, не относящиеся к какому-либо определенному типу данных. Чаще всего универсальные классы используются с коллекциями, такими как связанные списки, хэш-таблицы, очереди, стеки, деревья и т.п. Такие операции как добавление элементов в коллекцию или их удаление осуществляются одинаково вне зависимости от типа хранящихся данных.

Для большей части сценариев, в которых требуются классы коллекций, рекомендуется использовать классы, входящие в библиотеку классов платформы .NET Framework. Дополнительные сведения об использовании этих классов см. в разделе Универсальные шаблоны в библиотеке классов платформы .NET Framework (Руководство по программированию в C#).

Как правило, создания универсальных классов начинается с запуска существующего класса и изменения типов на параметры типов по одному до тех пор, пока не будет достигнуто оптимальное соотношение между универсальностью и удобством. При создании собственных универсальных классов следует учитывать следующее:

  • Какие типы преобразовывать в параметры.

    Как правило, чем больше типов преобразовано в параметры, тем более гибким становится программный код. Однако излишняя универсальность использование ключевого слова может сделать код более трудным для понимания другими разработчиками.

  • Какие ограничения применяются к параметрам типов (см. Ограничения параметров типа (руководство по программированию в C#)).

    Рекомендуется применять наибольшее возможное число ограничений, при котором обеспечивается обработка нужных типов. Например, если известно, что универсальных класс нужен только для ссылочных типов, можно применить ограничение класса. Это предотвратит использования класса с типами значений и позволит использовать оператор as для T и проверять нулевые значения.

  • Следует ли разделять поведение универсального класса на базовые классы и подклассы.

    Универсальные классы могут служить базовыми классами, поэтому при их создании следует учитывать такие же обстоятельства, как при создании классов, не являющихся универсальными. См. правила наследования от универсальных базовых классов далее в этом разделе.

  • Следует ли реализовывать один или несколько универсальных интерфейсов.

    Например, при создании класса, который будет использован для создания элементов в универсальной коллекции, может понадобиться реализовать интерфейс, подобный IComparable<T>, где T — тип вашего класса.

Пример простого универсального класса см. в разделе Введение в универсальные шаблоны. (Руководство по программированию на C#).

В правилах, действующих для параметров типов и ограничений, действует несколько неявных принципов поведения универсальных классов, особенно в отношении наследования и доступа к членам. Перед тем, как продолжить, следует уяснить некоторые моменты. Для универсального класса Node<T>, клиентский код может ссылаться на класс путем указания аргумента типа, чтобы создать закрытый тип (Node<int>). Также можно не указывать параметр типа, чтобы создать открытый тип (Node<T>). Универсальные классы могут быть унаследованы от конкретных закрытых или открытых базовых классов.

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> { }

Классы, не являющиеся универсальными, то есть конкретные классы, могут быть унаследованы от закрытых базовых классов, но не от открытых базовых классов и от параметров типов, поскольку во время выполнения клиентский код не может предоставить аргумент типа, необходимый для создания экземпляра базового класса.

//No error
class Node1 : BaseNodeGeneric<int> { }

//Generates an error
//class Node2 : BaseNodeGeneric<T> {}

//Generates an error
//class Node3 : T {}

Универсальные классы, унаследованные от открытых базовых классов, должны предоставлять аргументы типа для всех параметров типа базового класса, к которым нет доступа у наследующего класса, как показано в следующем примере кода.

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> {} 

Универсальные классы, унаследованные от открытых базовых классов, должны указывать ограничения, которые соответствуют ограничениям базового типа или являются более строгими.

class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }

Универсальные типы могут использовать несколько параметров типа и ограничений.

class SuperKeyType<K, V, U>
    where U : System.IComparable<U>
    where V : new()
{ }

Открытые и закрытые типы можно использовать в качестве параметров методов.

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
}

Если универсальных класс реализует интерфейс, все экземпляры этого класса могут быть приведены к этому интерфейсу.

Универсальные классы инвариантны. Другими словами, если входной параметр указывает List<BaseClass>, при попытке предоставить List<DerivedClass> возникнет ошибка компиляции.

См. также

Основные понятия

Руководство по программированию в C#

Ссылки

Универсальные шаблоны (Руководство по программированию на C#)

System.Collections.Generic

Другие ресурсы

Сохранение состояния перечислителей

Пазл наследования, часть 1