where (ограничение универсального типа) (справочник по C#)
Предложение where
в универсальном определении задает ограничения на типы, которые используются в качестве аргументов для параметров типа в универсальном типе, методе, делегате или локальной функции. Ограничения могут задавать интерфейсы, базовые классы или требовать, чтобы универсальный тип был ссылочным типом, типом значения или неуправляемым типом. Они объявляют возможности, которые должны иметь аргумент типа, и должны быть помещены после любого объявленного базового класса или реализованных интерфейсов.
Например, можно объявить универсальный класс AGenericClass
так, чтобы параметр типа T
реализовывал интерфейс IComparable<T>:
public class AGenericClass<T> where T : IComparable<T> { }
Примечание.
Дополнительные сведения о предложении where в выражении запроса см. в разделе Предложение where.
Предложение where
также может включать ограничение базового класса. Ограничение базового класса указывает, что тип, который должен использоваться как аргумент типа для этого универсального типа, имеет заданный класс в качестве базового класса или является этим базовым классом. Если ограничение базового класса используется, оно должно быть указано перед любыми другими ограничениями данного параметра типа. Некоторые типы не могут использоваться как ограничение базового класса: Object, Array и ValueType. Ниже приведен пример типов, которые теперь можно указать как базовый класс:
public class UsingEnum<T> where T : System.Enum { }
public class UsingDelegate<T> where T : System.Delegate { }
public class Multicaster<T> where T : System.MulticastDelegate { }
В контексте, допускающего значение NULL, применяется допустимость значений NULL типа базового класса. Если базовый класс не допускает значения NULL (например, Base
), аргумент типа должен иметь значение, отличное от NULL. Если базовый класс имеет значение NULL (например Base?
, аргумент типа может быть пустым или не допускаемым значением NULL ссылочного типа). Компилятор выдает предупреждение, если аргумент типа является ссылочным типом, допускающим значения NULL, когда базовый класс не допускает значения NULL.
Предложение where
может указывать, что тип является class
или struct
. Ограничение struct
избавляет от необходимости указывать ограничение базового класса System.ValueType
. Тип System.ValueType
нельзя использовать в качестве ограничения базового класса. Ограничения class
и struct
показаны в следующем примере:
class MyClass<T, U>
where T : class
where U : struct
{ }
В контексте, допускающем значение NULL, class
ограничение требует, чтобы тип был ссылочным типом, не допускающим значение NULL. Чтобы разрешить ссылочные типы, допускающие значения NULL, используйте ограничение class?
, которое разрешает ссылочные типы, допускающие и не допускающие значения NULL.
Предложение where
может включать notnull
ограничение. Ограничение notnull
ограничивает параметр типа типами, допускающими значение NULL. Тип может быть типом значения или ссылочным типом, не допускаемым значением NULL. Ограничение notnull
доступно для кода, скомпилированного в контекстеnullable enable
. В отличие от других ограничений, если аргумент типа нарушает ограничение notnull
, компилятор генерирует предупреждение вместо ошибки. Предупреждения генерируются только в контексте nullable enable
.
Добавление ссылочных типов, допускающих значения NULL, приводит к возможной неоднозначности значения T?
в универсальных методах. Если T
имеет значение struct
, то T?
соответствует System.Nullable<T>. Но если T
имеет ссылочный тип, для T?
допустимо значение null
. Так как переопределяющиеся методы не могут включать в себя ограничения, возникает неоднозначность. Новое ограничение default
устраняет ее. Вы добавляете его, когда базовый класс или интерфейс объявляет две перегрузки метода, один из которых задает struct
ограничение, и тот, который не имеет примененного struct
или class
ограничения:
public abstract class B
{
public void M<T>(T? item) where T : struct { }
public abstract void M<T>(T? item);
}
Используйте ограничение default
, чтобы указать, что производный класс переопределяет метод без ограничения в производном классе, или явную реализацию интерфейса. Оно допустимо только для методов, переопределяющих базовые методы, или явных реализаций интерфейса:
public class D : B
{
// Without the "default" constraint, the compiler tries to override the first method in B
public override void M<T>(T? item) where T : default { }
}
Внимание
Универсальные объявления, включающие ограничение notnull
, можно использовать в обнуляемом контексте, допускающем значение NULL, но компилятор не применяет ограничение.
#nullable enable
class NotNullContainer<T>
where T : notnull
{
}
#nullable restore
Предложение where
также может включать unmanaged
ограничение. Ограничение unmanaged
позволяет использовать в качестве параметра типа только типы, называемые неуправляемыми типами. Ограничение unmanaged
упрощает написание кода взаимодействия низкого уровня на языке C#. Это ограничение включает подпрограммы с возможностью повторного использования для всех неуправляемых типов. Ограничение unmanaged
нельзя использовать с ограничением class
или struct
. Ограничение unmanaged
требует тип struct
:
class UnManagedWrapper<T>
where T : unmanaged
{ }
Предложение where
также может включать ограничение конструктора. new()
Это ограничение позволяет создать экземпляр параметра типа с помощью оператора new
. Ограничение new() сообщает компилятору о том, что все предоставленные аргументы типа должны иметь доступный конструктор без параметров. Например:
public class MyGenericClass<T> where T : IComparable<T>, new()
{
// The following line is not possible without new() constraint:
T item = new T();
}
Ограничение new()
отображается последним в where
предложении, если за ним не следует allows ref struct
анти-ограничение. Ограничение new()
не может использоваться с ограничениями struct
или unmanaged
. Все типы, удовлетворяющие этим ограничениям, должны иметь доступ к конструктору без параметров, поэтому ограничение new()
будет избыточным.
Это анти-ограничение объявляет, что аргумент T
типа для может быть типом ref struct
. Например:
public class GenericRefStruct<T> where T : allows ref struct
{
// Scoped is allowed because T might be a ref struct
public void M(scoped T parm)
{
}
}
Универсальный тип или метод должен соответствовать правилам безопасности ссылок для любого экземпляра T
, так как это может быть ref struct
. Предложение allows ref struct
не может сочетаться с или class?
ограничениемclass
. Анти-ограничение allows ref struct
должно соответствовать всем ограничениям для этого аргумента типа.
Если параметров типа несколько, для каждого из них необходимо использовать по одному предложению where
, например:
public interface IMyInterface { }
namespace CodeExample
{
class Dictionary<TKey, TVal>
where TKey : IComparable<TKey>
where TVal : IMyInterface
{
public void Add(TKey key, TVal val) { }
}
}
Кроме того, ограничения можно присоединять к параметрам типа универсальных методов следующим образом:
public void MyMethod<T>(T t) where T : IMyInterface { }
Обратите внимание на то, что ограничения параметров типа для делегатов имеют такой же синтаксис, как и методы:
delegate T MyDelegate<T>() where T : new();
Дополнительные сведения об универсальных делегатах см. в разделе Универсальные делегаты.
Дополнительные сведения о синтаксисе и применении ограничений см. в разделе Ограничения параметров типа.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.