Универсальные шаблоны в .NET

Универсальные шаблоны позволяют точно настроить метод, класс, структуру или интерфейс в соответствии с типом обрабатываемых данных. Например, вместо использования класса Hashtable, который разрешает ключам и значениям иметь любой тип, можно использовать универсальный класс Dictionary<TKey,TValue> и указать типы, разрешенные для ключа и значения. Помимо прочего, преимуществами универсальных шаблонов являются улучшенная возможность многократного использования кода и сохранения типов.

Определение и использование универсальных шаблонов

Универсальными шаблонами являются классы, структуры, интерфейсы и методы, которые имеют прототипы (параметры типов) для одного или нескольких типов, которые они хранят или используют. Класс универсальной коллекции может использовать параметр типа в качестве заполнителя для типа объектов, которые в нем хранятся. Параметры типа отображаются как типы его полей и типы параметров его методов. Универсальный метод может использовать параметр типа в качестве типа возвращаемого значения или как тип одного из своих формальных параметров. Следующий код иллюстрирует определение простого универсального класса.

generic<typename T>
public ref class Generics
{
public:
    T Field;
};
public class Generic<T>
{
    public T Field;
}
Public Class Generic(Of T)
    Public Field As T

End Class

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

static void Main()
{
    Generics<String^>^ g = gcnew Generics<String^>();
    g->Field = "A string";
    //...
    Console::WriteLine("Generics.Field           = \"{0}\"", g->Field);
    Console::WriteLine("Generics.Field.GetType() = {0}", g->Field->GetType()->FullName);
}
public static void Main()
{
    Generic<string> g = new Generic<string>();
    g.Field = "A string";
    //...
    Console.WriteLine("Generic.Field           = \"{0}\"", g.Field);
    Console.WriteLine("Generic.Field.GetType() = {0}", g.Field.GetType().FullName);
}
Public Shared Sub Main()
    Dim g As New Generic(Of String)
    g.Field = "A string"
    '...
    Console.WriteLine("Generic.Field           = ""{0}""", g.Field)
    Console.WriteLine("Generic.Field.GetType() = {0}", g.Field.GetType().FullName)
End Sub

Терминология универсальных шаблонов

В контексте универсальных шаблонов в .NET используются следующие термины:

  • Определение универсального типа — это объявление класса, структуры или интерфейса, которое работает в качестве шаблона с прототипами для типов, которые он может содержать или использовать. Например, класс System.Collections.Generic.Dictionary<TKey,TValue> может содержать два типа: ключи и значения. Поскольку определение универсального типа — это только шаблон, создавать экземпляры класса, структуры или интерфейса, являющиеся определением универсального типа, нельзя.

  • Параметры универсального типа или параметры типа являются прототипами в определении универсального типа или метода. Универсальный тип System.Collections.Generic.Dictionary<TKey,TValue> имеет два параметра типа TKey и TValue, которые представляют типы его ключей и значений.

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

  • Аргумент универсального типа является любым типом, заменяемым на параметр универсального типа.

  • Общий термин универсальный тип описывает определения как сконструированных типов, так и универсальных типов.

  • Ковариация и контравариация параметров универсального типа позволяют использовать сконструированные универсальные типы, аргументы типов которых находятся на более высоком (в случае ковариации) или низком (в случае контравариации) уровне иерархии наследования, чем у целевого сконструированного типа. Вместе ковариантность и контрвариантность называются вариацией. Дополнительные сведения см. в разделе Ковариация и контравариантность.

  • Ограничения — это пределы, наложенные на параметры универсального типа. Например, можно ограничить параметр типа типами, реализующими универсальный интерфейс System.Collections.Generic.IComparer<T> , чтобы обеспечить упорядочивание экземпляров типа. Можно также ограничить параметры типа типами, имеющими определенный базовый класс, который содержит конструктор без параметров, или типами, являющимися ссылочными типами или типами значений. Пользователи универсального типа не могут подставить аргументы типа, которые не удовлетворяют ограничениям.

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

generic<typename T>
T Generic(T arg)
{
    T temp = arg;
    //...
    return temp;
}
T Generic<T>(T arg)
{
    T temp = arg;
    //...
    return temp;
}
Function Generic(Of T)(ByVal arg As T) As T
    Dim temp As T = arg
    '...
    Return temp
End Function

Универсальные методы могут присутствовать в универсальных и неуниверсальных типах. Важно отметить, что метод не является универсальным только потому, что он принадлежит универсальному типу или даже в том случае, если он имеет формальные параметры, типы которых являются универсальными параметрами для включающего их типа. Метод является универсальным только в том случае, если он имеет свой собственный список параметров типа. В следующем коде только метод G является универсальным.

ref class A
{
    generic<typename T>
    T G(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
};
generic<typename T>
ref class Generic
{
    T M(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
};
class A
{
    T G<T>(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
}
class Generic<T>
{
    T M(T arg)
    {
        T temp = arg;
        //...
        return temp;
    }
}
Class A
    Function G(Of T)(ByVal arg As T) As T
        Dim temp As T = arg
        '...
        Return temp
    End Function
End Class
Class Generic(Of T)
    Function M(ByVal arg As T) As T
        Dim temp As T = arg
        '...
        Return temp
    End Function
End Class

Преимущества и недостатки универсальных шаблонов

Использование универсальных коллекций и делегатов предоставляет целый ряд преимуществ.

  • Типобезопасность. Универсальные шаблоны позволяют передать компилятору обязанности обеспечения типовой безопасности. Нет необходимости написания кода для проверки правильности типа данных, так как проверка происходит во время компиляции. Уменьшается потребность приведения типов и вероятность ошибок во время выполнения.

  • Объем кода уменьшен и поддерживает многократную реализацию. Нет необходимости наследования базового типа и преобладающих членов. Например, LinkedList<T> готов к немедленному использованию. Например, можно создать связанный список строк со следующим объявлением переменной:

    LinkedList<String^>^ llist = gcnew LinkedList<String^>();
    
    LinkedList<string> llist = new LinkedList<string>();
    
    Dim llist As New LinkedList(Of String)()
    
  • Повышенная производительность. Универсальные типы коллекций имеют более высокую производительность при хранении и управлении типами значений, поскольку отсутствует необходимость их упаковки.

  • Универсальные делегаты поддерживают типобезопасные обратные вызовы без необходимости создания нескольких классов делегатов. Например, универсальный делегат Predicate<T> позволяет создать метод, который реализует собственные условия поиска для определенного типа, и использовать ваш метод с методами типа Array , такими как Find, FindLastи FindAll.

  • Универсальные шаблоны упрощают динамически создаваемый код. Универсальные делегаты можно также использовать в динамически создаваемом коде без необходимости создания типа делегата. Это увеличивает количество ситуаций, в которых можно использовать облегченные динамические методы вместо создания целых сборок. Дополнительные сведения см. в разделе Практическое руководство. Определение и выполнение динамических методов и DynamicMethod.

Ниже перечислены некоторые ограничения универсальных шаблонов.

  • Универсальные типы могут быть наследованы от большинства базовых классов, таких как MarshalByRefObject (а ограничения могут использоваться для обеспечения того, чтобы параметры универсальных типов наследовались от базовых классов, таких как MarshalByRefObject). Тем не менее, .NET не поддерживает контекстно-связанные универсальные типы. Универсальный тип может быть производным от ContextBoundObject, но при попытке создать экземпляр этого типа будет создано исключение TypeLoadException.

  • Перечисления не могут иметь параметров универсального типа. Перечисление может быть универсальным только случайно (например, если оно является вложенным в универсальный тип, который определен с помощью Visual Basic, C# или C++). Дополнительные сведения см. в разделе "Перечисления" статьи Система общих типов CTS.

  • Облегченные динамические методы не могут быть универсальными.

  • В Visual Basic, C# и C++ вложенный тип, заключенный в универсальном типе, не может быть использован для создания объекта, кроме тех случаев, когда типы были назначены параметрам типа всех заключенных типов. Другими словами, вложенный тип, который определен с помощью этих языков, включает параметры типов всех его заключенных типов. Это позволяет использовать параметры типа заключающих типов в определениях членов вложенного типа. Дополнительные сведения см. в подразделе "Вложенные типы" раздела MakeGenericType.

    Примечание

    Вложенный тип, который определяется путем создания кода в динамической сборке или с помощью ассемблера IL (Ilasm.exe), не обязательно должен включать параметры его заключающих типов. Тем не менее, если он их не включает, параметры типов находятся вне области вложенного класса.

    Дополнительные сведения см. в подразделе "Вложенные типы" раздела MakeGenericType.

Библиотека классов и языковая поддержка

.NET предоставляет ряд универсальных классов коллекций в следующих пространствах имен:

  • В пространстве имен System.Collections.Generic содержится большинство типов универсальных коллекций, предоставляемых в составе .NET, например универсальные классы List<T> и Dictionary<TKey,TValue> .

  • В пространстве имен System.Collections.ObjectModel содержатся дополнительные типы универсальных коллекций (например, универсальный класс ReadOnlyCollection<T>), которые удобно использовать для предоставления объектных моделей пользователям классов.

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

В пространство имен System.Reflection добавлена поддержка универсальных шаблонов для обеспечения возможности проверки универсальных типов и универсальных методов, в пространство имен System.Reflection.Emit добавлена поддержка шаблонов для создания динамических сборок, содержащих универсальные типы и методы, в пространство имен System.CodeDom добавлена поддержка шаблонов для создания графов исходного кода, включающих универсальные шаблоны.

Среда CLR предоставляет новые коды операций и префиксы для поддержки универсальных типов в языке MSIL, включая Stelem, Ldelem, Unbox_Any, Constrainedи Readonly.

В языках Visual C++, C# и Visual Basic обеспечивается полноценная поддержка определения и использования универсальных шаблонов. Дополнительные сведения о поддержке языков см. в статьях Универсальные типы в Visual Basic, Введение в универсальные шаблоны и Обзор универсальных типов в Visual C++.

Вложенные типы и универсальные шаблоны

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

Заголовок Описание
Универсальные коллекции в .NET Описание классов универсальных коллекций и других универсальных типов в составе .NET.
Универсальные методы-делегаты для управления массивами и списками Описание универсальных делегатов для преобразований, предикатов поиска и действий, осуществляемых с элементами массива или коллекции.
Универсальные интерфейсы Описание универсальных интерфейсов, которые предоставляют общие функции для различных семейств универсальных типов.
Ковариация и контрвариантность Описание ковариации и контравариации в параметрах универсальных типов.
Часто используемые типы коллекций Предоставление сводных данных о характеристиках и сценариях использования типов коллекций в составе .NET, включая универсальные типы.
Когда следует использовать универсальные коллекции Описание общих правил для определения ситуаций, когда следует использовать универсальные типы коллекций.
Практическое руководство. Определение универсального типа с порождаемым отражением Описание способов создания динамических сборок, содержащих универсальные типы и методы.
Generic Types in Visual Basic Описание универсальных шаблонов для пользователей Visual Basic, включая практические руководства об использовании и определении универсальных типов.
Введение в универсальные шаблоны Общие сведения об определении и использовании универсальных типов для пользователей C#.
Обзор универсальных типов в Visual C++ Описание универсальных шаблонов для пользователей C++, включая описание различий между шаблонами и универсальными шаблонами.

Справочник

System.Collections.Generic

System.Collections.ObjectModel

System.Reflection.Emit.OpCodes