Ограничения параметров типа (руководство по программированию на 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. Компилятор может только предполагать членов Object, который является главным базовым классом для всех типов .NET.The compiler can only assume the members of Object, which is the ultimate base class for any .NET type. Дополнительные сведения см. в статье Зачем использовать ограничения.For more information, see Why use constraints. Если в клиентском коде для создания экземпляра класса используется недопустимый тип, возникает ошибка времени компиляции.If client code tries to instantiate your class by using a type that is not allowed by a constraint, the result is a compile-time error. Ограничения задаются с помощью контекстного ключевого слова where.Constraints are specified by using the where contextual keyword. В следующей таблице описываются семь типов ограничений:The following table lists the seven types of constraints:

ОграничениеConstraint Описание:Description
where T : struct Аргумент типа должен быть типом значения.The type argument must be a value type. Допускается задание любого типа значения, кроме Nullable.Any value type except Nullable can be specified. Дополнительные сведения см. в разделе Использование допускающих значение NULL типов.For more information, see Using Nullable Types.
where T : class Аргумент типа должен быть ссылочным типом.The type argument must be a reference type. Это ограничение также применяется к любому типу класса, интерфейса, делегата или массива.This constraint applies also to any class, interface, delegate, or array type.
where T : unmanaged Аргумент типа не может быть ссылочным типом и содержать члены ссылочного типа на любом уровне вложения.The type argument must not be a reference type and must not contain any reference type members at any level of nesting.
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.
where T : <имя базового класса>where T : <base class name> Аргумент типа должен иметь базовый класс или производный от него класс.The type argument must be or derive from the specified base class.
where T : <имя интерфейса>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.
where T : U Аргумент типа, указанный для T, должен быть аргументом, указанным для U, или производным от него.The type argument supplied for T must be or derive from the argument supplied for U.

Некоторые ограничения являются взаимоисключающими.Some of the constraints are mutually exclusive. Все типы значений должны иметь доступ к конструктору без параметров.All value types must have an accessible parameterless constructor. Ограничение struct подразумевает ограничение new(), и ограничение new() не может использоваться с ограничением struct.The struct constraint implies the new() constraint and the new() constraint cannot be combined with the struct constraint. Ограничение unmanaged подразумевает ограничение struct.The unmanaged constraint implies the struct constraint. Ограничение unmanaged не может использоваться с ограничениями struct или new().The unmanaged constraint cannot be combined with either the struct or new() constraints.

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

Ограничивая параметр типа, вы расширяете выбор разрешенных операций и вызовов метода до поддерживаемых ограничивающим типом и всеми типами в его иерархии наследования.By constraining the type parameter, you increase the number of allowable operations and method calls to those supported by the constraining type and all types in its inheritance hierarchy. Если при разработке универсальных классов или методов планируется выполнение любых других операций с универсальными элементами, помимо простого присвоения или вызова методов, не поддерживаемых System.Object, вам необходимо применить ограничения к параметру типа.When you design generic classes or methods, if you will be performing any operation on the generic members beyond simple assignment or calling any methods not supported by System.Object, you will 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 s, int i) => (Name, ID) = (s, i);
    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 cannot be used because there is 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.

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

Начиная с C# 7.3 можно использовать ограничение unmanaged, чтобы указать, что параметр типа должен быть неуправляемым типом.Beginning with C# 7.3, you can use the unmanaged constraint to specify that the type parameter must be an unmanaged type. Неуправляемый тип — это тип, который не является ссылочным и не содержит поля ссылочного типа на любом уровне вложения.An unmanaged type is a type that is not a reference type and doesn't contain reference type fields at any level of nesting. Ограничение 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.

Ограничения делегата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 are 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 are 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;
}

В этих методах используется отражение, которое влияет на производительность.The methods used make use of reflection, which has performance implications. Вы можете вызвать этот метод для создания коллекции, которая кэшируется и используется повторно, вместо того, чтобы повторять вызовы, требующие отражения.You can call this method 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

System.Collections.Generic Руководство по программированию на C#System.Collections.Generic C# Programming Guide
Введение в универсальные шаблоныIntroduction to Generics
Универсальные классыGeneric Classes
Ограничение newnew Constraint