Ссылки на типы статического класса
Ссылка имени пространства имен или типа на статический класс (§3.8) допускается в следующих случаях.
· Имя пространства имен или типа T в имени пространства имен или типа формы T.I или
· Имя пространства имен или типа равно T в выражении typeof (§7.5.11) в виде typeof(T).
Ссылка первичного выражения на статический класс (§7.5) допускается в следующих случаях.
· Первичное выражение равно E в доступе к членам (§7.5.4) в форме E.I.
В любом другом контексте ссылка на статический класс является ошибкой времени компилирования. Например, использование статического класса в качестве базового класса, составного типа (§10.3.8) члена, аргумента универсального типа или ограничения параметра типа является ошибкой. Аналогичным образом статический класс не может использоваться в типе массива, типе указателя, выражении new, выражении приведения, выражении is, выражении as, выражении sizeof или в выражении значения по умолчанию.
Модификатор partial
Модификатор partial используется для указания того, что данное объявление класса является объявлением разделяемого типа. Несколько объявлений разделяемого типа с одинаковым именем в рамках заключающего объявления пространства имен или типа формируют объявление одного типа, согласно правилам в разделе §10.2.
Распределение объявления класса в отдельных сегментах текста программы может быть полезным при создании и поддержке этих сегментов в различных контекстах. Например, одна часть объявления класса может быть создана системой, а другая часть – создана вручную. Текстовое разделение двух частей позволяет предотвратить конфликт обновлений разных частей.
Параметры типа
Параметром типа является простой идентификатор, обозначающий заполнитель для аргумента типа, предоставленного для создания сформированного типа. Параметром типа является формальный заполнитель для типа, предоставляемого позже. И наоборот, аргументом типа (§4.4.1) является фактический тип, заменяемый для параметра типа при создании сформированного типа.
type-parameter-list:
< type-parameters >
type-parameters:
attributesopt type-parameter
type-parameters , attributesopt type-parameter
type-parameter:
identifier
Каждый параметр типа в объявлении класса определяет имя в области объявления (§3.3) данного класса. Поэтому невозможно задать аналогичное имя для другого параметра типа или члена, объявленного в данном классе. Параметр типа не может иметь одинаковое с самим типом имя.
Спецификация базы класса
Объявление класса может содержать спецификацию базы класса, определяющую прямой базовый класс для класса и интерфейсов (§13), непосредственно реализованных классом.
class-base:
: class-type
: interface-type-list
: class-type , interface-type-list
interface-type-list:
interface-type
interface-type-list , interface-type
Базовый класс, указанный в объявлении класса, может являться типом сформированного класса (§4.4). Базовый класс не может быть параметром типа сам по себе, хотя он может вовлекать параметры типа в области.
class Extend<V>: V {} // Error, type parameter used as base class
Базовые классы
Если тип класса включен в базу класса, он определяет прямой базовый класс объявленного класса. Если объявление класса не имеет базы классы или база класса перечисляет только типы интерфейса, прямым базовым классом считается object. Класс наследует члены из прямого базового класса (см. §10.3.3).
В этом примере
class A {}
class B: A {}
В этом примере класс A считается прямым базовым классом для класса B, а класс B считается производным из класса A. Так как класс A не указывает явно прямой базовый класс, его неявным прямым базовым классом является object.
Для сформированного типа класса, если базовый класс указан в объявлении универсального класса, базовый класс сформированного типа получается путем замещения каждого параметра типа в объявлении базового класса соответствующим аргументом типа сформированного типа. При объявлении универсального класса
class B<U,V> {...}
class G<T>: B<string,T[]> {...}
базовый класс сформированного типа G<int> должен быть B<string,int[]>.
Прямой базовый класс типа класса должен быть не менее доступен, чем сам тип класса (§3.5.2). Например, если класс public произведен из класса private или internal, возникает ошибка времени компилирования.
Прямой базовый класс типа класса не должен быть одним из следующих типов: System.Array, System.Delegate, System.MulticastDelegate, System.Enum или System.ValueType. Кроме того, объявление универсального класса не может использовать System.Attribute в качестве прямого или непрямого базового класса.
При определении значения спецификации прямого базового класса A для класса B прямым базовым классом B временно считается object. Это означает, что значение спецификации базового класса не может рекурсивно зависеть от самого себя. Пример:
class A<T> {
public class B{}
}
class C : A<C.B> {}
является ошибочным, поскольку в спецификации базового класса A<C.B>прямым базовым классом для C считается object, а следовательно (согласно правилам §3.8) считается, что C не содержит член B.
Базовыми классами типа класса являются прямой базовый класс и его базовые классы. Другими словами, набор базовых классов является транзитивным замыканием отношения прямого базового класса. Ссылаясь на пример выше, базовыми классами для класса B являются A и object. В этом примере
class A {...}
class B<T>: A {...}
class C<T>: B<IComparable<T>> {...}
class D<T>: C<T[]> {...}
базовыми классами D<int> являются классы C<int[]>, B<IComparable<int[]>>, A и object.
За исключением класса object, каждый тип класса имеет строго один прямой базовый класс. Класс object не имеет прямого базового класса и является окончательным базовым классом для всех других классов.
Если класс B производится из класса A, возникает ошибка времени компилирования, так как A зависит от B. Класс непосредственно зависит от его прямого базового класса (при его наличии) и от класса, в который он непосредственно вложен (при его наличии). С учетом этого определения, полным набором классов, от которых зависит класс, является рефлексивное и транзитивное замыкание отношения непосредственной зависимости.
Пример:
class A: A {}
Этот пример является ошибочным, поскольку класс зависит сам от себя. Аналогично, в примере
class A: B {}
class B: C {}
class C: A {}
имеется ошибка, поскольку классы циклически зависят сами от себя. Наконец, в примере
class A: B.C {}
class B: A
{
public class C {}
}
имеется ошибка времени компилирования, так как класс A зависит от класса B.C (его прямой базовый класс), который зависит от класса B (его прямой заключающий класс), который зависит от класса A, создавая циклическую зависимость.
Обратите внимание, что класс не зависит от классов, вложенных в него. В этом примере
class A
{
class B: A {}
}
B зависит от A (так как A является его прямым базовым классом и прямым заключающим классом), но A не зависит от B (так как B не является базовым классом и заключающим классом A). Поэтому пример является допустимым.
Невозможно выполнить создание производного класса от запечатанного класса sealed. В этом примере
sealed class A {}
class B: A {} // Error, cannot derive from a sealed class
класс B является ошибочным, так как он пытается наследовать от класса A с модификатором sealed.
Реализация интерфейсов
Спецификация базы класса может включать список типов интерфейса, при этом считается, что класс непосредственно реализует заданные типы интерфейса. Реализация интерфейсов рассматривается более подробно в §13.4.
Ограничения параметров типа
Объявления универсального типа и метода могут дополнительно указывать ограничения параметра типа, включая предложения ограничений параметров типа.
type-parameter-constraints-clauses:
type-parameter-constraints-clause
type-parameter-constraints-clauses type-parameter-constraints-clause
type-parameter-constraints-clause:
where type-parameter : type-parameter-constraints
type-parameter-constraints:
primary-constraint
secondary-constraints
constructor-constraint
primary-constraint , secondary-constraints
primary-constraint , constructor-constraint
secondary-constraints , constructor-constraint
primary-constraint , secondary-constraints , constructor-constraint
primary-constraint:
class-type
class
struct
secondary-constraints:
interface-type
type-parameter
secondary-constraints , interface-type
secondary-constraints , type-parameter
constructor-constraint:
new ( )
Каждое предложение ограничений параметров типа состоит из маркера where, следующих затем имени параметра типа, двоеточия и списка ограничений для данного параметра типа. Может существовать не более одного предложения where для каждого параметра типа, порядок перечисления предложений where не имеет существенного значения. Аналогично токенам get и set в методе доступа к свойству, токен where не является ключевым словом.
Список ограничений в предложении where может включать любые из представленных ниже компонентов в следующем порядке: одно первичное ограничение, одно или несколько вторичных ограниченийи ограничение конструктораnew().
Первичное ограничение может быть типом класса или ограничением ссылочного типа class или ограничением типа значения struct. Вторичное ограничение может быть параметром типа или типом интерфейса.
Ограничение ссылочного типа указывает, что аргумент типа, используемый для параметра типа, должен быть типом ссылки. Все типы классов, интерфейсов, делегатов, массивов и параметры типов считаются ссылочными типами (см. ниже), удовлетворяющих данному ограничению.
Ограничение типа значения указывает, что аргумент типа, используемый для параметра типа, не должен быть типом, который может иметь значение null. Все типы структур, которые не могут иметь значение null, типы перечислений и параметры типа с ограничением типа значения удовлетворяют данному ограничению. Обратите внимание, что, несмотря на классификацию в качестве типа значения, тип, который может иметь значение NULL (§4.1.10), не удовлетворяет ограничению типа значения. Параметр типа с ограничением типа значения также не может иметь ограничение конструктора.
Типы указателей никогда не могут быть аргументами типа и не удовлетворяют ограничениям ссылочного типа или типа значения.
Если ограничение является типом класса, типом интерфейса или параметром типа, данный тип указывает минимальный «базовый тип», который должен поддерживать каждый аргумент типа для данного параметра типа. При использовании сформированного типа или универсального метода аргумент типа проверяется на соответствие ограничениям параметра типа во время компиляции. Предоставленный аргумент типа должен удовлетворять условиям, определенным в разделе 4.4.4.
Ограничение типа класса должно удовлетворять следующим правилам.
· Типом должен быть тип класса.
· Тип не должен иметь модификатор sealed.
· Тип должен быть одним из следующих типов: System.Array, System.Delegate System.Enum или System.ValueType.
· Тип не должен совпадать с object. Так как все типы являются производными от object, отсутствие данного ограничения не оказывает никакого влияния.
· Максимум одно ограничение для данного параметра типа может являться типом класса.
Тип, заданный в качестве ограничения типа интерфейса, должен удовлетворять следующим правилам:
· Он должен быть типом интерфейса.
· Тип не должен быть указан более одного раза в данном предложении where.
В любом случае ограничение может включать любые параметры типа связанных объявлений типа или метода в качестве части сформированного типа, а также может включать объявляемый тип.
Любой тип класса или интерфейса, указанный в качестве ограничения параметра типа, должен быть не менее доступен (§3.5.4), чем объявляемый универсальный тип или метод.
Тип, заданный в качестве ограничения параметра типа, должен удовлетворять следующим правилам.
· Он должен быть параметром типа.
· Тип не должен быть указан более одного раза в данном предложении where.
Кроме того, не допускается наличие циклов в диаграмме зависимости параметров типа, где зависимостью является транзитивное отношение, заданное следующим.
· Если параметр типа T используется в качестве ограничения для параметра типа S, то S зависит от T.
· Если параметр типа S зависит от параметра типа T и T зависит от параметра типа U, то S зависит от U.
Согласно данному отношению, если параметр типа зависит от самого себя (прямым или косвенным образом), это является ошибкой времени компилирования.
Любые ограничения должны быть согласованы среди зависимых параметров типа. Если параметр типа S зависит от параметра типа T, выполняется следующее.
· T не должен иметь ограничение типа значения. В противном случае T эффективно запечатывается таким образом, что S будет принудительно того же типа, что и T, устраняя тем самым потребность в двух параметрах типа.
· Если S имеет ограничение типа значения, то T не должен иметь ограничения типа класса.
· Если S имеет ограничение типа класса A и T имеет ограничение типа класса B, то требуется преобразование идентификации или неявные преобразования ссылочных типов из A в B (или из B в A).
· Если S также зависит от параметра типа U, U имеет ограничение типа класса A и T имеет ограничение типа класса B, то требуется преобразование идентификации или неявные преобразования ссылочных типов из A в B (или из B в A).
S может иметь ограничение типа значения, и T может иметь ограничение ссылочного типа. Фактически это позволяет ограничить T типами System.Object, System.ValueType, System.Enum и любыми типами интерфейса.
Если предложение where для параметра типа включает ограничение конструктора (с формой new()), можно использовать оператор new для создания экземпляров типа (§7.6.10.1). Любой аргумент типа, используемый для параметра типа с ограничением конструктора, должен иметь открытый конструктор без параметров (данный конструктор неявно существует для любого типа значения) или должен быть параметром типа с ограничением типа значения или ограничением конструктора (подробные сведения см. в разделе §10.1.5).
Ниже представлены примеры ограничений.
interface IPrintable
{
void Print();
}
interface IComparable<T>
{
int CompareTo(T value);
}
interface IKeyProvider<T>
{
T GetKey();
}
class Printer<T> where T: IPrintable {...}
class SortedList<T> where T: IComparable<T> {...}
class Dictionary<K,V>
where K: IComparable<K>
where V: IPrintable, IKeyProvider<K>, new()
{
...
}
Следующий пример содержит ошибку, так как он вызывает цикличность в графе зависимостей параметров типа.
class Circular<S,T>
where S: T
where T: S // Error, circularity in dependency graph
{
...
}
Следующие примеры иллюстрируют некоторые недопустимые ситуации.
class Sealed<S,T>
where S: T
where T: struct // Error, T is sealed
{
...
}
class A {...}
class B {...}
class Incompat<S,T>
where S: A, T
where T: B // Error, incompatible class-type constraints
{
...
}
class StructWithClass<S,T,U>
where S: struct, T
where T: U
where U: A // Error, A incompatible with struct
{
...
}
Эффективный базовый класс параметра типа T определяется следующим образом.
· Если T не имеет первичных ограничений или ограничений параметра типа, его эффективным базовым классом является object.
· Если T имеет ограничение типа значения, его фактическим базовым классом является System.ValueType.
· Если T имеет ограничение типа класса C, но не имеет ограничений параметра типа, его эффективным базовым классом является C.
· Если T не имеет ограничения типа класса, но имеет одно или более ограничений параметра типа, его эффективным базовым классом является наивысший заключенный тип (§6.4.2) в наборе эффективных базовых классов его ограничений параметра типа. Правила соответствия обеспечивают существование наивысшего заключенного типа.
· Если T имеет ограничение типа класса и одно или более ограничений параметра типа, его эффективным базовым классом является наивысший заключенный тип (§6.4.2) в наборе, состоящем из ограничения типа класса T и эффективных базовых классов его ограничений параметра типа. Правила соответствия обеспечивают существование наивысшего заключенного типа.
· Если T имеет ограничение ссылочного типа, но не имеет ограничений типа класса, его эффективным базовым классом является object.
Для выполнения этих правил, если T имеет ограничение V, имеющее тип значения, используйте более конкретный базовый тип V типа класса. Это не может произойти в явно предоставленном ограничении, но может случиться, когда ограничения универсального метода неявным образом наследуются объявлением метода переопределения или явной реализацией метода интерфейса.
Эти правила обеспечивают то, что эффективным базовым классом всегда является тип класса.
Эффективный набор интерфейса параметра типа T определяется следующим образом.
· Если T не имеет вторичных ограничений, его эффективный набор интерфейсов пуст.
· Если T имеет ограничения типа интерфейса, но не имеет ограничений параметра типа, его эффективным набором интерфейса является его набор ограничений типа интерфейса.
· Если T не имеет ограничений типа интерфейса, но имеет ограничения параметра типа, его эффективным набор интерфейса является объединение эффективных наборов интерфейса его ограничений параметра типа.
· Если T имеет сразу ограничения типа интерфейса и ограничения параметра типа, его эффективным набор интерфейса является объединение ограничений типа интерфейса и эффективных наборов интерфейса его ограничений параметра типа.
Параметр типа считается ссылочным типом, если он имеет ограничение ссылочного типа, или если его эффективным базовым классом не является object или System.ValueType.
Значения ограниченного типа для типа параметра могут использоваться для доступа к членам экземпляров, подразумеваемых ограничениями. В этом примере
interface IPrintable
{
void Print();
}
class Printer<T> where T: IPrintable
{
void PrintOne(T x) {
x.Print();
}
}
методы интерфейса IPrintable могут быть вызваны непосредственно для x, так как, в силу ограничения, T обязан постоянно реализовывать IPrintable.
Тело класса
Тело класса для класса определяет члены этого класса.
class-body:
{ class-member-declarationsopt }
Разделяемые типы
Объявление типа может быть разделено на несколько объявлений разделяемого типа. Объявление типа формируется из его частей в соответствии с правилами данного раздела, после чего оно считается единым объявлением на протяжении оставшегося времени компилирования и выполнения программы.
Объявление класса, структуры или интерфейса представляет объявление разделяемого типа, если оно включает модификатор partial. Модификатор partial не является ключевым словом и действует в качестве модификатора, только если он расположен непосредственно перед одним из ключевых слов class, struct или interface в объявлении типа или перед типом void в объявлении метода. В других контекстах его можно использовать в качестве обычного идентификатора.
Каждая часть объявления разделяемого типа должна содержать модификатор partial. Он должен иметь такое же имя и должен быть объявлен в том же объявлении пространства имен или типа, что и другие части. Модификатор partial указывает, что могут существовать дополнительные части объявления типа, но их существование не является обязательным; тип с одним объявлением может содержать модификатор partial.
Все части разделяемого типа должны быть скомпилированы вместе, чтобы они могли быть объединены во время компилирования в одно объявление типа. Разделяемые типы специально не допускают расширение уже скомпилированных типов.
Вложенные типы могут быть объявлены в нескольких частях путем использования модификатора partial. Обычно тип-контейнер объявляется также посредством partial, а каждая часть вложенного типа объявляется в другой части типа-контейнера.
Модификатор partial не допускается в объявлениях делегата или перечисляемого типа.
Атрибуты
Атрибуты разделяемого типа определяются путем комбинирования в неопределенном порядке атрибутов каждой части. Если атрибут размещен в нескольких частях, он эквивалентен многократному указанию атрибута для типа. Например, две части:
[Attr1, Attr2("hello")]
partial class A {}
[Attr3, Attr2("goodbye")]
partial class A {}
эквиваленты такому объявлению:
[Attr1, Attr2("hello"), Attr3, Attr2("goodbye")]
class A {}
Атрибуты параметров типа комбинируются аналогичным образом.
Модификаторы
Если объявление разделяемого типа включает спецификацию доступности (модификаторы public, protected, internal и private), оно должно быть согласовано со всеми другими частями, включающими спецификацию доступности. Если ни одна из частей разделяемого типа не содержит спецификацию доступности, типу задается соответствующая доступность по умолчанию (§3.5.1).
Если одно или несколько разделяемых объявлений вложенного типа включают модификатор new, и если вложенный тип скрывает унаследованный член (§3.7.1.2), предупреждение не дается.
Если одно или несколько разделяемых объявлений класса включают модификатор abstract, класс считается абстрактным (§10.1.1.1). В противном случае класс считается неабстрактным.
Если одно или несколько разделяемых объявлений класса включают модификатор sealed, класс считается запечатанным (§10.1.1.2). В противном случае класс считается незапечатанным.
Обратите внимание, что класс не может быть одновременно абстрактным и запечатанным.
При использовании модификатора unsafe в объявлении разделяемого типа только соответствующая часть считается небезопасным контекстом (§18.1).