Поделиться через


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

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

Для большей части сценариев, в которых требуются классы коллекций, рекомендуется использовать классы, входящие в библиотеку классов платформы .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#)

System.Collections.Generic

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

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

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

Saving the State of Enumerators

An Inheritance Puzzle, Part One