Параметры и ограничения типа

Если универсальный тип объявлен в нескольких частях, каждая часть должна формулировать параметры типа. Все части должны иметь одинаковое количество параметров типа и одинаковые имена для каждого параметра типа (по порядку).

Если объявление разделяемого универсального типа содержит ограничения (предложения where), ограничения должны быть согласованы со всеми другими частями, включающими ограничения. В частности, каждая часть, содержащая ограничения, должна иметь ограничения для того же набора параметров типа, а для каждого параметра типа наборы первичных, вторичных ограничений и ограничений конструктора должны быть эквивалентны. Два набора ограничений эквивалентны, если они содержат одинаковые члены. Если ни одна часть разделяемого универсального типа не указывает ограничения параметра типа, параметры типа считаются неограниченными.

Пример:

partial class Dictionary<K,V>
where K: IComparable<K>
where V: IKeyProvider<K>, IPersistable
{
...
}

partial class Dictionary<K,V>
where V: IPersistable, IKeyProvider<K>
where K: IComparable<K>
{
...
}

partial class Dictionary<K,V>
{
...
}

Этот пример является верным, так как части, включающие ограничения (первые две), фактически задают один и тот же набор первичных, вторичных ограничений и ограничений конструктора для одного и того же набора параметров типа, соответственно.

Базовый класс

Если объявление разделяемого класса содержит спецификацию базового класса, требуется согласование со всеми остальными частями, включающими спецификацию базового класса. Если ни одна часть разделяемого класса не включает спецификацию базового класса, базовым классом становится System.Object (§10.1.4.1).

Базовые интерфейсы

Набором базовых интерфейсов для типа, объявленного в нескольких частях, является объединение базовых интерфейсов, указанных в каждой части. Определенный базовый интерфейс может быть именован только один раз в каждой части, но при этом нескольким частям разрешается именовать соответствующие базовые интерфейсы. Может быть только одна реализация членов любого заданного базового интерфейса.

В этом примере

partial class C: IA, IB {...}

partial class C: IC {...}

partial class C: IA, IB {...}

набором базовых интерфейсов для класса C является IA, IB и IC.

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

partial class X
{
int IComparable.CompareTo(object o) {...}
}

partial class X: IComparable
{
...
}

Члены

Исключая разделяемые методы (§10.2.7), набором членов типа, объявленного в нескольких частях, является просто объединение членов, объявленных в каждой части. Тела всех частей объявления типа совместно используют одну область объявления (§3.3), а область каждого члена (§3.7) расширяется на тела всех частей. Домен доступности любого члена всегда содержит все части вмещающего типа; член private, объявленный в одной части, свободно доступен из другой части. Объявление одного члена в нескольких частях типа приводит к ошибке времени компиляции, если этот член не является типом с модификатором partial.

partial class A
{
int x; // Error, cannot declare x more than once

partial class Inner // Ok, Inner is a partial type
{
int y;
}
}

partial class A
{
int x; // Error, cannot declare x more than once

partial class Inner // Ok, Inner is a partial type
{
int z;
}
}

Порядок членов в типе редко имеет значение в коде C#, однако он может стать важным при взаимодействии с другими языками и средами. В таких случаях порядок членов в рамках типа, объявленного в нескольких частях, не определен.

Разделяемые методы

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

Разделяемые методы не могут определять модификаторы доступа, но неявно являются private. Типом возвращаемого значения разделяемого метода должен быть void, а параметры не могут иметь модификатора out. Идентификатор partial определяется в качестве специального ключевого слова в объявлении метода, только если он появляется непосредственно перед типом void, в противном случае он может использоваться в качестве обычного идентификатора. Разделяемый метод не может явно реализовывать методы интерфейсов.

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



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

Реализующее объявление разделяемого метода может содержаться в той же части, что и соответствующее определяющее объявление разделяемого метода.

Только определяющий разделяемый метод участвует в разрешении перегрузки. Таким образом, независимо от наличия реализующего объявления, выражения вызова могут обращаться к вызовам разделяемого метода. Поскольку разделяемый метод всегда возвращает значение void, такие выражения вызова всегда будут являться операторами выражения. Кроме того, так как разделяемый метод является неявно private, такие операторы всегда будут возникать в той части объявления типа, в рамках которой объявлен разделяемый метод.

Если ни одна из частей объявления разделяемого типа не содержит реализующее объявление для заданного разделяемого метода, любой оператор выражения, вызывающий ее, просто удаляется из комбинированного объявления типа. Таким образом, выражение вызова, включая любые групповые выражения, не оказывает воздействия во время выполнения. Разделяемый метод сам по себе также удаляется и не будет членом скомбинированного объявления типа.

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

  • Модификатор partial не включается.
  • Атрибутами в результирующем объявлении метода являются скомбинированные атрибуты определяющего и реализующего объявлений разделяемого метода в неопределенном порядке. Дубликаты не удаляются.
  • Атрибутам параметров в результирующем объявлении метода являются скомбинированные атрибуты соответствующих параметров определяющего и реализующего объявлений разделяемого метода в неопределенном порядке. Дубликаты не удаляются.

Если для разделяемого метода M задано определяющее объявление, а реализующее объявление не задано, применяются следующие ограничения.

  • Создание делегата метода является ошибкой времени компилирования (§7.6.10.5).
  • Ссылка на M внутри анонимной функции, преобразованной в тип дерева выражений, является ошибкой времени компилирования (§6.5.2).
  • Выражения, возникающие в качестве части вызова M, не воздействуют на состояние определенного присваивания (§5.3), которое может привести к ошибкам времени компилирования.
  • Метод M не может являться точкой входа приложения (§3.1).

Разделяемые методы полезны, если одна часть объявления типа должна управлять поведением другой части (например, сгенерированной программным средством). Обратите внимание на следующее объявление разделяемого класса:

partial class Customer
{
string name;

public string Name {

get { return name; }

set {
OnNameChanging(value);
name = value;
OnNameChanged();
}

}

partial void OnNameChanging(string newName);

partial void OnNameChanged();
}

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

class Customer
{
string name;

public string Name {

get { return name; }

set { name = value; }
}
}

Предположим, что задана другая часть, предоставляющая реализующие объявления разделяемых методов:

partial class Customer
{
partial void OnNameChanging(string newName)
{
Console.WriteLine(“Changing “ + name + “ to “ + newName);
}

partial void OnNameChanged()
{
Console.WriteLine(“Changed to “ + name);
}
}

Тогда результирующее объявление скомбинированного класса будет эквивалентно следующему:

class Customer
{
string name;

public string Name {

get { return name; }

set {
OnNameChanging(value);
name = value;
OnNameChanged();
}

}

void OnNameChanging(string newName)
{
Console.WriteLine(“Changing “ + name + “ to “ + newName);
}

void OnNameChanged()
{
Console.WriteLine(“Changed to “ + name);
}
}

Привязка имен

Несмотря на то, что каждая часть расширяемого типа должна быть объявлена в одном пространстве имен, части обычно прописываются в рамках различных объявлений пространств имен. Поэтому для каждой части могут присутствовать различные директивы using (§9.4). При интерпретации простых имен (§7.5.2) в рамках одной части учитываются только директивы using объявлений пространства имен, заключающие данную часть. Это может привести к наличию одинаковых идентификаторов с различными значениями в различных частях.

namespace N
{
using List = System.Collections.ArrayList;

partial class A
{
List x; // x has type System.Collections.ArrayList
}
}

namespace N
{
using List = Widgets.LinkedList;

partial class A
{
List y; // y has type Widgets.LinkedList
}
}

Члены класса

Члены класса состоят из членов, представленных его объявлениями членов класса и членами, унаследованными от прямого базового класса.

class-member-declarations:
class-member-declaration
class-member-declarations class-member-declaration

class-member-declaration:
constant-declaration
field-declaration
method-declaration
property-declaration
event-declaration
indexer-declaration
operator-declaration
constructor-declaration
destructor-declaration
static-constructor-declaration
type-declaration

Члены типа класса разделены на следующие категории.

· Константы, представляющие постоянные значения, связанные с классом (§10.4).

· Поля, являющиеся переменными класса (§10.5).

· Методы, реализующие вычисления и действия, которые могут быть выполнены классом (§10.6).

· Свойства, определяющие именованные характеристики и действия, связанные с чтением и записью данных характеристик (§10.7).

· События, определяющие уведомления, которые могут быть сгенерированы классом (§10.8).

· Индексаторы, которые обеспечивают индексацию экземпляров класса аналогично (синтаксически) индексации массивов (§10.9).

· Операторы, определяющие операторы выражений, которые могут быть применены к экземплярам класса (§10.10).

· Конструкторы экземпляров, реализующие действия, требуемые для инициализации экземпляров класса (§10.11)

· Деструкторы, реализующие действия, выполняемые до отмены экземпляров класса без возможности восстановления (§10.13).

· Статические конструкторы, реализующие действия, требуемые для инициализации самого класса (§10.12)

· Типы, представляющие локальные типы класса (§10.3.8).

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

Объявление класса создает новую область объявления (§3.3), а объявления члена класса, непосредственно содержащиеся в объявлении класса, представляют новые члены в данной области объявления. К объявлениям члена класса применяются следующие правила.

· Конструкторы экземпляров, деструкторы и статические конструкторы должны иметь одинаковые имена с именами непосредственного заключающего класса. Все другие члены должны иметь имена, отличающиеся от имен непосредственного заключающего класса.

· Имена константы, поля, свойства, события или типа должны отличаться от имен всех других членов, объявленных в том же классе.

· Имя метода должно отличаться от всех других объявленных в том же классе имен, не относящихся к методам. Кроме того, подпись (§3.6) метода должна отличаться от подписей всех других методов, объявленных в этом же классе, а два метода, объявленные в одном и том же классе, не могут иметь подписи, отличающиеся только параметрами ref и out.

· Сигнатура конструктора экземпляра должна отличаться от сигнатур всех других конструкторов экземпляров, объявленных в том же классе, а два конструктора экземпляра, объявленных в одном классе, не могут иметь сигнатуры, отличающиеся только словами ref и out.

· Сигнатура индексатора должна отличаться от сигнатур всех других индексаторов, объявленных в этом же классе.

· Сигнатура оператора должна отличаться от сигнатур всех других операторов, объявленных в том же классе.

Унаследованные члены типа класса (§10.3.3) не являются частью области объявления класса. Поэтому производный класс может объявлять член с именем или сигнатурой, совпадающей с именем унаследованного члена (что приводит к скрытию унаследованного члена).

Тип экземпляра

Каждое объявление класса имеет связанный привязанный тип (§4.4.3), тип экземпляра. Для объявления универсального класса тип экземпляра формируется путем создания сформированного типа (§4.4) на основе объявления типа с каждым предоставленным аргументом типа, являющимся соответствующим параметром типа. Поскольку тип экземпляра использует параметры типа, он может быть использован только в том случае, когда параметры типа находятся в его области, т. е. в рамках объявления класса. Типом экземпляра является this для кода, написанного внутри объявления класса. Для неуниверсальных классов типом экземпляра является просто объявленный класс. Далее представлены объявления класса с их типами экземпляров.

class A<T> // instance type: A<T>
{
class B {} // instance type: A<T>.B

class C<U> {} // instance type: A<T>.C<U>
}

class D {} // instance type: D

Члены сформированных типов

Не унаследованные члены сформированного типа получаются путем замещения для каждого параметра типа в объявлении члена соответствующего аргумента типа сформированного типа. Процесс замещения основан на семантическом значении объявлений типа и не является простым текстовым замещением.

Например, в объявлении универсального класса

class Gen<T,U>
{
public T[,] a;

public void G(int i, T t, Gen<U,T> gt) {...}

public U Prop { get {...} set {...} }

public int H(double d) {...}
}

сформированный тип Gen<int[],IComparable<string>> имеет следующие члены

public int[,][] a;

public void G(int i, int[] t, Gen<IComparable<string>,int[]> gt) {...}

public IComparable<string> Prop { get {...} set {...} }

public int H(double d) {...}

Типом члена a в объявлении универсального класса Gen является «двумерный массив T», поэтому типом члена a в сформированном выше типе является «двумерный массив одномерного массива int», или int[,][].

В рамках членов функции экземпляра типом this является тип экземпляра (§10.3.1) содержащего объявления.

Все члены универсального класса могут использовать параметры типа из любого включающего класса непосредственно или в качестве части сформированного типа. Когда определенный закрытый сформированный тип (§4.4.2) используется во время выполнения, каждое использование параметра типа заменяется фактическим аргументом типа, предоставляемым сформированному типу. Пример:

class C<V>
{
public V f1;
public C<V> f2 = null;

public C(V x) {
this.f1 = x;
this.f2 = this;
}
}

class Application
{
static void Main() {
C<int> x1 = new C<int>(1);
Console.WriteLine(x1.f1); // Prints 1

C<double> x2 = new C<double>(3.1415);
Console.WriteLine(x2.f1); // Prints 3.1415
}
}

Наследование

Класс наследует члены своего прямого типа базового класса. Наследование означает, что класс неявно содержит все члены его прямого типа базового класса, исключая конструкторы экземпляров, деструкторы и статические конструкторы базового класса. Далее перечислены некоторые важные аспекты наследования.

· Наследование транзитивно. Если C наследуется от B, а B наследуется от A, то C унаследует члены, объявленные и в B, и в A.

· Производный класс расширяет свой прямой базовый класс. Производный класс может добавлять новые члены к своим унаследованным членам, но он не может удалить определение унаследованного члена.

· Конструкторы экземпляров, деструкторы и статические конструкторы не наследуются, но все другие члены, независимо от их объявленной доступности, наследуются (§3.5). Но в зависимости от их объявленной доступности, унаследованные члены могут быть недоступны в производном классе.

· Производный класс может скрывать (§3.7.1.2) унаследованные члены путем объявления новых членов с тем же именем или с той же сигнатурой. Обратите внимание, что скрытие унаследованного члена не приводит к удалению данного члена, оно просто приводит к тому, что этот член становится недоступен непосредственно через производный класс.

· Экземпляр класса содержит набор всех полей экземпляра, объявленных в классе и его базовом классе, и существует неявное преобразование (§6.1.6) из типа производного класса в любой из его типов базового класса. Таким образом, ссылка на экземпляр некоторого производного класса может рассматриваться как ссылка на экземпляр какого-либо из его базовых классов.

· Класс может объявить виртуальные методы, свойства и индексаторы, а производный класс может переопределить реализацию данных членов функций. Это позволяет классам реализовывать полиморфное поведение, при котором действия, выполняемые вызовом члена функции, отличаются в зависимости от типа времени выполнения экземпляра, в котором вызывается член функции.

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

class B<U>
{
public U F(long index) {...}
}

class D<T>: B<T[]>
{
public T G(string s) {...}
}

В предыдущем примере сформированный тип D<int> имеет неунаследованный член public int G(string s), получаемый путем замещения аргумента типа int на параметр типа T.D<int>, который также имеет наследуемый член из объявления класса B. Данный унаследованный член определяется путем определения типа базового класса B<int[]> из D<int> посредством замещения int для T в спецификации базового класса B<T[]>. Затем в качестве аргумента типа B int[] заменяет U в строке public U F(long index), что приводит к унаследованному члену public int[] F(long index).

Модификатор new

Для объявления члена класса допускается объявление члена с таким же именем или сигнатурой, что и у унаследованного члена. При этом член производного класса скрывает член базового класса. Скрытие унаследованного члена не считается ошибкой, но приводит к отображению компилятором предупреждения. Чтобы эти предупреждения не появлялись, объявление члена производного класса может включать модификатор new для указания того, что производный член предназначен для скрытия базового члена. Эта тема более подробно рассматривается в §3.7.1.2.

Если модификатор new включен в объявление, не скрывающее унаследованный член, создается предупреждение об этом эффекте. Чтобы не выводить такие предупреждения, следует удалить модификатор new.

Модификаторы доступа

Объявление члена класса может иметь любой из пяти возможных видов объявленной доступности (§3.5.1): public, protected internal, protected, internal или private. Исключая комбинацию protected internal, указание более одного модификатора доступа является ошибкой времени компилирования. Если объявление члена класса не включает какие-либо модификаторы доступа, предполагается использование модификатора private.

Составные типы

Типы, используемые в объявлении члена, являются составными типами данного члена. Возможные составные члены — это типы константы, поля, свойства, события или индексатора, тип возвращаемого значения метода или оператора и типы параметров метода, индексатора, оператора или конструктора экземпляров. Составные типы члена должны быть не менее доступны, чем сам член (§3.5.4).

Наши рекомендации