Ограничения параметров типа (руководство по программированию на C#)Constraints on type parameters (C# Programming Guide)

Ограничения сообщают компилятору о характеристиках, которые должен иметь аргумент типа.Constraints inform the compiler about the capabilities a type argument must have. Без ограничений аргумент типа может быть любым типом.Without any constraints, the type argument could be any type. Компилятор может только предполагать членов System.Object, который является главным базовым классом для всех типов .NET.The compiler can only assume the members of System.Object, which is the ultimate base class for any .NET type. Дополнительные сведения см. в статье Зачем использовать ограничения.For more information, see Why use constraints. Если клиентский код использует тип, который не удовлетворяет ограничению, компилятор выдает ошибку.If client code uses a type that doesn't satisfy a constraint, the compiler issues an error. Ограничения задаются с помощью контекстного ключевого слова where.Constraints are specified by using the where contextual keyword. В следующей таблице описываются различные типы ограничений:The following table lists the various types of constraints:

ОграничениеConstraint ОписаниеDescription
where T : struct Аргумент типа должен быть типом значения, не допускающим значения NULL.The type argument must be a non-nullable value type. См. дополнительные сведения о типах значений, допускающих значение NULL.For information about nullable value types, see Nullable value types. Поскольку все типы значений имеют доступный конструктор без параметров, ограничение struct подразумевает наличие ограничения new() и не может применяться в сочетании с ограничением new().Because all value types have an accessible parameterless constructor, the struct constraint implies the new() constraint and can't be combined with the new() constraint. Ограничение struct нельзя использовать вместе с ограничением unmanaged.You can't combine the struct constraint with the unmanaged constraint.
where T : class Аргумент типа должен быть ссылочным типом.The type argument must be a reference type. Это ограничение также применяется к любому типу класса, интерфейса, делегата или массива.This constraint applies also to any class, interface, delegate, or array type. В контексте, допускающем значения NULL, в C# 8.0 или более поздней версии T должен быть ссылочным типом, не допускающим значения NULL.In a nullable context in C# 8.0 or later, T must be a non-nullable reference type.
where T : class? Аргумент типа должен быть ссылочным типом, допускающим значения NULL или не допускающим значения NULL.The type argument must be a reference type, either nullable or non-nullable. Это ограничение также применяется к любому типу класса, интерфейса, делегата или массива.This constraint applies also to any class, interface, delegate, or array type.
where T : notnull Аргумент типа должен быть типом, не допускающим значения NULL.The type argument must be a non-nullable type. Аргумент может быть ссылочным типом, не допускающим значения NULL в C# 8.0 или более поздней версии или типом значения, не допускающим значения NULL.The argument can be a non-nullable reference type in C# 8.0 or later, or a non-nullable value type.
where T : unmanaged Аргумент типа должен быть неуправляемым типом, не допускающим значения NULL.The type argument must be a non-nullable unmanaged type. Ограничение unmanaged подразумевает ограничение struct и не может использоваться совместно с ограничением struct или new().The unmanaged constraint implies the struct constraint and can't be combined with either the struct or new() constraints.
where T : new() Аргумент типа должен иметь общий конструктор без параметров.The type argument must have a public parameterless constructor. При одновременном использовании нескольких ограничений последним должно указываться ограничение new().When used together with other constraints, the new() constraint must be specified last. Ограничение new() не может использоваться с ограничениями struct и unmanaged.The new() constraint can't be combined with the struct and unmanaged constraints.
where T : <base class name>where T : <base class name> Аргумент типа должен иметь базовый класс или производный от него класс.The type argument must be or derive from the specified base class. В контексте, допускающем значение NULL, в C# 8.0 и более поздней версии T должен быть ссылочным типом, не допускающим значения NULL, производным от указанного базового класса.In a nullable context in C# 8.0 and later, T must be a non-nullable reference type derived from the specified base class.
where T : <base class name>?where T : <base class name>? Аргумент типа должен иметь базовый класс или производный от него класс.The type argument must be or derive from the specified base class. В контексте, допускающем значения NULL, в C# 8.0 и более поздней версии T может быть типом, допускающим значения NULL или не допускающим значения NULL, производным от указанного базового класса.In a nullable context in C# 8.0 and later, T may be either a nullable or non-nullable type derived from the specified base class.
where T : <interface name>where T : <interface name> Аргумент типа должен являться заданным интерфейсом или реализовывать его.The type argument must be or implement the specified interface. Можно указать несколько ограничений интерфейса.Multiple interface constraints can be specified. Заданный в ограничении интерфейс также может быть универсальным.The constraining interface can also be generic. В контексте, допускающем значения NULL, в C# 8.0 и более поздней версии T должен быть типом, не допускающим значения NULL, который реализует указанный интерфейс.In a nullable context in C# 8.0 and later, T must be a non-nullable type that implements the specified interface.
where T : <interface name>?where T : <interface name>? Аргумент типа должен являться заданным интерфейсом или реализовывать его.The type argument must be or implement the specified interface. Можно указать несколько ограничений интерфейса.Multiple interface constraints can be specified. Заданный в ограничении интерфейс также может быть универсальным.The constraining interface can also be generic. В контексте, допускающем значения NULL, в C# 8.0 T может быть ссылочным типом, допускающим значения NULL, ссылочным типом, не допускающим значения NULL, или типом значения.In a nullable context in C# 8.0, T may be a nullable reference type, a non-nullable reference type, or a value type. T не может быть типом значения, допускающим значения NULL.T may not be a nullable value type.
where T : U Аргумент типа, указанный для T, должен быть аргументом, указанным для U, или производным от него.The type argument supplied for T must be or derive from the argument supplied for U. Если в контексте, допускающем значения NULL, U является ссылочным типом, допускающим значения NULL, T должен быть ссылочным типом, не допускающим значения NULL.In a nullable context, if U is a non-nullable reference type, T must be non-nullable reference type. Если U является ссылочным типом, допускающим значения NULL, T может допускать или не допускать значения NULL.If U is a nullable reference type, T may be either nullable or non-nullable.

Зачем использовать ограниченияWhy use constraints

Ограничения определяют возможности и ожидания параметра типа.Constraints specify the capabilities and expectations of a type parameter. Объявление этих ограничений означает, что можно использовать операции и вызовы методов ограничивающего типа.Declaring those constraints means you can use the operations and method calls of the constraining type. Если универсальный класс или метод использует любые другие операции с универсальными элементами, помимо простого присвоения или вызова методов, не поддерживаемых System.Object, вам необходимо применить ограничения к параметру типа.If your generic class or method uses any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you'll have to apply constraints to the type parameter. Например, на основе ограничения базового класса компилятор определяет, что в качестве аргументов типа будут использоваться только объекты указанного типа или производных от него.For example, the base class constraint tells the compiler that only objects of this type or derived from this type will be used as type arguments. Имея такую гарантию, компилятор может вызывать методы указанного типа в универсальном классе.Once the compiler has this guarantee, it can allow methods of that type to be called in the generic class. В следующем примере кода показаны функциональные возможности, которые можно добавить в класс GenericList<T> (см. раздел Введение в универсальные шаблоны), применив ограничение базового класса.The following code example demonstrates the functionality you can add to the GenericList<T> class (in Introduction to Generics) by applying a base class constraint.

public class Employee
{
    public Employee(string name, int id) => (Name, ID) = (name, id);
    public string Name { get; set; }
    public int ID { get; set; }
}

public class GenericList<T> where T : Employee
{
    private class Node
    {
        public Node(T t) => (Next, Data) = (null, t);

        public Node Next { get; set; }
        public T Data { get; set; }
    }

    private Node head;

    public void AddHead(T t)
    {
        Node n = new Node(t) { Next = head };
        head = n;
    }

    public IEnumerator<T> GetEnumerator()
    {
        Node current = head;

        while (current != null)
        {
            yield return current.Data;
            current = current.Next;
        }
    }

    public T FindFirstOccurrence(string s)
    {
        Node current = head;
        T t = null;

        while (current != null)
        {
            //The constraint enables access to the Name property.
            if (current.Data.Name == s)
            {
                t = current.Data;
                break;
            }
            else
            {
                current = current.Next;
            }
        }
        return t;
    }
}

Это ограничение позволяет универсальному классу использовать свойство Employee.Name.The constraint enables the generic class to use the Employee.Name property. Ограничение указывает, что все элементы типа T гарантированно являются либо объектом Employee, либо объектом, который наследует от Employee.The constraint specifies that all items of type T are guaranteed to be either an Employee object or an object that inherits from Employee.

К одному параметру типа можно применять несколько ограничений, которые сами по себе могут быть универсальными типами, как показано ниже:Multiple constraints can be applied to the same type parameter, and the constraints themselves can be generic types, as follows:

class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
    // ...
}

При применении ограничения where T : class не рекомендуется использовать операторы == и != для параметра типа, поскольку в этом случае будет проверяться только удостоверение ссылки, а не равенство значений.When applying the where T : class constraint, avoid the == and != operators on the type parameter because these operators will test for reference identity only, not for value equality. Такое поведение будет наблюдаться даже в том случае, если эти операторы будут перегружены в типе, используемом в качестве аргумента.This behavior occurs even if these operators are overloaded in a type that is used as an argument. Эта особенность показана в следующем коде, который будет возвращать значение false даже в том случае, если класс String перегружает оператор ==.The following code illustrates this point; the output is false even though the String class overloads the == operator.

public static void OpEqualsTest<T>(T s, T t) where T : class
{
    System.Console.WriteLine(s == t);
}

private static void TestStringEquality()
{
    string s1 = "target";
    System.Text.StringBuilder sb = new System.Text.StringBuilder("target");
    string s2 = sb.ToString();
    OpEqualsTest<string>(s1, s2);
}

Компилятору известно только то, что T является ссылочным типом во время компиляции, и он должен использовать операторы по умолчанию, которые действительны для всех ссылочных типов.The compiler only knows that T is a reference type at compile time and must use the default operators that are valid for all reference types. Чтобы проверить равенство значений, рекомендуется применить ограничение where T : IEquatable<T> или where T : IComparable<T> и реализовать этот интерфейс в любом классе, который будет использоваться для создания универсального класса.If you must test for value equality, the recommended way is to also apply the where T : IEquatable<T> or where T : IComparable<T> constraint and implement the interface in any class that will be used to construct the generic class.

Ограничение нескольких параметровConstraining multiple parameters

Ограничения можно применить к нескольким параметрам. Кроме того, к одному параметру можно применять несколько ограничений, как показано в следующем примере:You can apply constraints to multiple parameters, and multiple constraints to a single parameter, as shown in the following example:

class Base { }
class Test<T, U>
    where U : struct
    where T : Base, new()
{ }

Несвязанные параметры типаUnbounded type parameters

Не имеющие ограничений параметры типа (например, T в общем классе SampleClass<T>{}) называются несвязанными.Type parameters that have no constraints, such as T in public class SampleClass<T>{}, are called unbounded type parameters. В отношении несвязанных параметров типа применяются следующие правила:Unbounded type parameters have the following rules:

  • Не допускается использование операторов != и ==, поскольку нет гарантии, что они будут поддерживаться конкретным аргументом типа.The != and == operators can't be used because there's no guarantee that the concrete type argument will support these operators.
  • Их можно преобразовать в System.Object или явно преобразовать в любой тип интерфейса.They can be converted to and from System.Object or explicitly converted to any interface type.
  • Можно сравнить их со значением null.You can compare them to null. При сравнении несвязанного параметра с null для аргументов типа, являющихся типами значений, всегда возвращается значение false.If an unbounded parameter is compared to null, the comparison will always return false if the type argument is a value type.

Параметры типа в качестве ограниченийType parameters as constraints

Использование параметров универсального типа в качестве ограничений применимо, когда функция-член со своим параметром типа должна ограничивать этот параметр параметром содержащего типа, как показано в следующем примере:The use of a generic type parameter as a constraint is useful when a member function with its own type parameter has to constrain that parameter to the type parameter of the containing type, as shown in the following example:

public class List<T>
{
    public void Add<U>(List<U> items) where U : T {/*...*/}
}

В предыдущем примере T является ограничением типа в контексте метода Add и несвязанным параметром типа в контексте класса List.In the previous example, T is a type constraint in the context of the Add method, and an unbounded type parameter in the context of the List class.

Параметры типа также можно использовать в определениях универсальных классов.Type parameters can also be used as constraints in generic class definitions. Параметр типа необходимо объявлять в угловых скобках вместе с любыми другими параметрами типа:The type parameter must be declared within the angle brackets together with any other type parameters:

//Type parameter V is used as a type constraint.
public class SampleClass<T, U, V> where T : V { }

Применение параметров типа в качестве ограничений для универсальных классов ограничено, поскольку в отношении таких параметров типа компилятор может предполагать только то, что они являются производными от System.Object.The usefulness of type parameters as constraints with generic classes is limited because the compiler can assume nothing about the type parameter except that it derives from System.Object. Параметры типа в качестве ограничений следует использовать в универсальных классах в тех случаях, когда необходимо обеспечить отношение наследования между двумя параметрами типа.Use type parameters as constraints on generic classes in scenarios in which you want to enforce an inheritance relationship between two type parameters.

Ограничение не NULLNotNull constraint

Начиная с C# 8.0 в контексте, допускающем значения NULL, можно использовать ограничение notnull, чтобы указать, что аргумент типа должен быть типом значения, не допускающим значения NULL, или ссылочным типом, не допускающим значения NULL.Beginning with C# 8.0 in a nullable context, you can use the notnull constraint to specify that the type argument must be a non-nullable value type or non-nullable reference type. Ограничение notnull может использоваться только в контексте nullable enable.The notnull constraint can only be used in a nullable enable context. Компилятор генерирует предупреждение, если вы добавляете ограничение notnull в контексте, который игнорирует допустимость значений NULL.The compiler generates a warning if you add the notnull constraint in a nullable oblivious context.

В отличие от других ограничений, когда аргумент типа нарушает ограничение notnull, компилятор генерирует предупреждение, когда этот код компилируется в контексте nullable enable.Unlike other constraints, when a type argument violates the notnull constraint, the compiler generates a warning when that code is compiled in a nullable enable context. Если код скомпилирован в контексте, который игнорирует допустимость значений NULL, компилятор не генерирует никаких предупреждений или ошибок.If the code is compiled in a nullable oblivious context, the compiler doesn't generate any warnings or errors.

Начиная с C# 8.0 в контексте, допускающем значения NULL, ограничение class указывает, что аргумент типа должен быть ссылочным типом, не допускающим значения NULL.Beginning with C# 8.0 in a nullable context, the class constraint specifies that the type argument must be a non-nullable reference type. Если в контексте, допускающем значения NULL, тип параметра является ссылочным типом, допускающим значения NULL, компилятор выдаст предупреждение.In a nullable context, when a type parameter is a nullable reference type, the compiler generates a warning.

Неуправляемое ограничениеUnmanaged constraint

Начиная с C# 7.3, можно использовать ограничение unmanaged, чтобы указать, что параметр типа должен быть неуправляемым типом, не допускающим значения NULL.Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be a non-nullable unmanaged type. Ограничение unmanaged позволяет создавать многократно используемые подпрограммы для работы с типами, которые могут обрабатываться как блоки памяти, как показано в следующем примере:The unmanaged constraint enables you to write reusable routines to work with types that can be manipulated as blocks of memory, as shown in the following example:

unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged
{
    var size = sizeof(T);
    var result = new Byte[size];
    Byte* p = (byte*)&argument;
    for (var i = 0; i < size; i++)
        result[i] = *p++;
    return result;
}

В примере выше метод необходимо компилировать в контексте unsafe, так как он использует оператор sizeof для типа, не известного как встроенный тип.The preceding method must be compiled in an unsafe context because it uses the sizeof operator on a type not known to be a built-in type. Без ограничения unmanaged оператор sizeof недоступен.Without the unmanaged constraint, the sizeof operator is unavailable.

Ограничение unmanaged подразумевает ограничение struct и не может использоваться совместно с ним.The unmanaged constraint implies the struct constraint and can't be combined with it. Поскольку ограничение struct подразумевает ограничение new(), ограничение unmanaged также не может использоваться с ограничением new().Because the struct constraint implies the new() constraint, the unmanaged constraint can't be combined with the new() constraint as well.

Ограничения делегатаDelegate constraints

Кроме того, начиная с C# 7.3 можно использовать System.Delegate или System.MulticastDelegate как ограничение базового класса.Also beginning with C# 7.3, you can use System.Delegate or System.MulticastDelegate as a base class constraint. В среде CLR это ограничение всегда было разрешено, но в языке C# оно было запрещено.The CLR always allowed this constraint, but the C# language disallowed it. Ограничение System.Delegate позволяет написать код, который работает с делегатами типобезопасным образом.The System.Delegate constraint enables you to write code that works with delegates in a type-safe manner. Следующий код определяет метод расширения, который объединяет два делегата при условии, что они одного типа:The following code defines an extension method that combines two delegates provided they're the same type:

public static TDelegate TypeSafeCombine<TDelegate>(this TDelegate source, TDelegate target)
    where TDelegate : System.Delegate
    => Delegate.Combine(source, target) as TDelegate;

Приведенный выше метод можно использовать для объединения делегатов, которые относятся к одному типу:You can use the above method to combine delegates that are the same type:

Action first = () => Console.WriteLine("this");
Action second = () => Console.WriteLine("that");

var combined = first.TypeSafeCombine(second);
combined();

Func<bool> test = () => true;
// Combine signature ensures combined delegates must
// have the same type.
//var badCombined = first.TypeSafeCombine(test);

Если раскомментировать последнюю строку, она не будет компилироваться.If you uncomment the last line, it won't compile. first и test являются типами делегатов, но это разные типы делегатов.Both first and test are delegate types, but they're different delegate types.

Ограничения перечисленияEnum constraints

Начиная с C# 7.3 можно указать тип System.Enum в качестве ограничения базового класса.Beginning in C# 7.3, you can also specify the System.Enum type as a base class constraint. В среде CLR это ограничение всегда было разрешено, но в языке C# оно было запрещено.The CLR always allowed this constraint, but the C# language disallowed it. Универсальные шаблоны с System.Enum предоставляют типобезопасное программирование для кэширования результатов использования статических методов в System.Enum.Generics using System.Enum provide type-safe programming to cache results from using the static methods in System.Enum. В следующем примере выполняется поиск всех допустимых значений для типа перечисления и создается словарь, который сопоставляет эти значения с их строковым представлением.The following sample finds all the valid values for an enum type, and then builds a dictionary that maps those values to its string representation.

public static Dictionary<int, string> EnumNamedValues<T>() where T : System.Enum
{
    var result = new Dictionary<int, string>();
    var values = Enum.GetValues(typeof(T));

    foreach (int item in values)
        result.Add(item, Enum.GetName(typeof(T), item));
    return result;
}

Enum.GetValues и Enum.GetName используют отражение, которое влияет на производительность.Enum.GetValues and Enum.GetName use reflection, which has performance implications. Вы можете не повторять вызовы, требующие отражения, а вызвать EnumNamedValues для создания коллекции, которая кэшируется и используется повторно.You can call EnumNamedValues to build a collection that is cached and reused rather than repeating the calls that require reflection.

Вы можете использовать его, как показано в следующем примере, для создания перечисления и словаря имен и значений:You could use it as shown in the following sample to create an enum and build a dictionary of its values and names:

enum Rainbow
{
    Red,
    Orange,
    Yellow,
    Green,
    Blue,
    Indigo,
    Violet
}
var map = EnumNamedValues<Rainbow>();

foreach (var pair in map)
    Console.WriteLine($"{pair.Key}:\t{pair.Value}");

См. такжеSee also