Инициализация статического поля
Инициализаторы переменных статических полейкласса соответствуют последовательности присваиваний, выполняемых в текстовом порядке, в котором они появляются в объявлении класса. Если в классе существует статический конструктор (§10.12), выполнение инициализаторов статического поля происходит непосредственно перед выполнением этого статического конструктора. Иначе инициализаторы статических полей выполняются в зависящий от реализации момент перед первым использованием статического поля этого класса. Пример:
using System;
class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}
public static int F(string s) {
Console.WriteLine(s);
return 1;
}
}
class A
{
public static int X = Test.F("Init A");
}
class B
{
public static int Y = Test.F("Init B");
}
В этом примере на выходе может быть
Init A
Init B
1 1
или
Init B
Init A
1 1
так как выполнение инициализатора X и инициализатора Y могло произойти в том или другом порядке; они только должны быть выполнены до обращений к этим полям. Но в примере
using System;
class Test
{
static void Main() {
Console.WriteLine("{0} {1}", B.Y, A.X);
}
public static int F(string s) {
Console.WriteLine(s);
return 1;
}
}
class A
{
static A() {}
public static int X = Test.F("Init A");
}
class B
{
static B() {}
public static int Y = Test.F("Init B");
}
результатом должно быть:
Init B
Init A
1 1
так как правила выполнения статических конструкторов (как определено в §10.12) обеспечивают, что статический конструктор для B (и следовательно инициализаторы статического поля B) должен выполняться до статического конструктора и инициализаторов поля A.
Инициализация поля экземпляра
Инициализаторы переменных полей экземпляров класса соответствуют последовательности присваиваний, выполняемых непосредственно при входе в любой из конструкторов экземпляров (§10.11.1) этого класса. Инициализаторы переменных выполняются в том текстовом порядке, в каком они представлены в объявлении класса. Создание экземпляра класса и процесс инициализации описаны далее в разделе §10.11.
Инициализатор переменной для поля экземпляра не может обращаться к создаваемому экземпляру. Таким образом, обращение к this в инициализаторе переменной, как и обращение инициализатора переменной к любому члену экземпляра по простому имени, является ошибкой времени компиляции. В этом примере
class A
{
int x = 1;
int y = x + 1; // Error, reference to instance member of this
}
инициализатор переменной для y приводит к ошибке времени компиляции, так как ссылается на член создаваемого экземпляра.
Методы
Метод — это член, реализующий вычисление или действие, которое может быть выполнено объектом или классом. Методы объявляются с помощью объявлений методов:
method-declaration:
method-header method-body
method-header:
attributesopt method-modifiersopt partialopt return-type member-name type-parameter-listopt
( formal-parameter-listopt ) type-parameter-constraints-clausesopt
method-modifiers:
method-modifier
method-modifiers method-modifier
method-modifier:
new
public
protected
internal
private
static
virtual
sealed
override
abstract
extern
async
return-type:
type
void
member-name:
identifier
interface-type . identifier
method-body:
block
;
Объявление метода может включать набор атрибутов (§17) допустимое сочетание любых из четырех модификаторов доступа (§10.3.5), а также модификаторы new (§10.3.4), static (§10.6.2), virtual (§10.6.3), override (§10.6.4), sealed (§10.6.5), abstract (§10.6.6) и extern (§10.6.7).
Объявление имеет допустимое сочетание модификаторов, если верно все следующее:
· объявление включает допустимое сочетание модификаторов доступа (§10.3.5);
· объявление не включает один и тот же модификатор несколько раз;
· объявление включает не более одного из следующих модификаторов: static, virtual и override;
· объявление включает не более одного из следующих модификаторов: new и override;
· если объявление включает модификатор abstract, оно не включает ни один из следующих модификаторов: static, virtual, sealed и extern;
· если объявление включает модификатор private, оно не включает ни один из следующих модификаторов: virtual, override и abstract;
· если объявление включает модификатор sealed, оно также включает модификатор override;
· если объявление включает модификатор partial, оно не включает ни один из следующих модификаторов: new, public, protected, internal, private, virtual, sealed, override, abstract и extern.
Метод с модификатором async является асинхронной функцией, которая следует правилам, описанным в разделе §10.14.
Тип возвращаемого значения в объявлении метода указывает тип значения, вычисляемого и возвращаемого методом. Если метод не возвращает значение, типом возвращаемого значения является void. Если объявление включает модификатор partial, типом возвращаемого значения должен быть void.
Имя члена указывает имя метода. Если только метод не является членом явной реализации интерфейса (§13.4.1), имя члена – это просто идентификатор. Для члена явной реализации интерфейса имя члена состоит из типа интерфейса, за которым следует «.» и идентификатор.
Необязательный список параметров типа указывает параметры типа метода (§10.1.3). Если список параметров типа указан, метод является универсальным методом. Если у метода есть модификатор extern, список параметров типа указывать нельзя.
Необязательный список формальных параметров указывает параметры метода (§10.6.1).
Необязательные предложения ограничений параметров типов указывают ограничения по отдельным параметрам типов (§10.1.5) и могут быть указаны, только если предоставлен также список параметров типов, а у метода нет модификатора override.
Тип возвращаемого значения и каждый из типов в списке формальных параметров метода должен быть, по крайней мере, так же доступным, как сам метод (см. §3.5.4).
Для методов abstract и extern тело метода состоит просто из точки с запятой. Для методов partial тело метода может состоять либо из точки с запятой, либо из блока. Для всех других методов тело метода состоит из блока, в котором указаны операторы для исполнения при вызове метода.
Если тело метода состоит из точки с запятой, объявление не может включать модификатор async.
Имя, список параметров типа и список формальных параметров метода определяют сигнатуру (§3.6) метода. А именно, сигнатура метода состоит из его имени, числа параметров типов и числа, модификаторов и типов его формальных параметров. Для этих целей каждый параметр типа метода, встречающийся в типе формального параметра, идентифицируется не по своему имени, а по своему порядковому номеру в списке аргументов типа метода. Тип возвращаемого значения не является частью сигнатуры метода, как и имена параметров типов и формальных параметров.
Имя метода должно отличаться от всех других объявленных в том же классе имен, не относящихся к методам. Кроме того, подпись метода должна отличаться от подписей всех других методов, объявленных в этом же классе, а два метода, объявленные в одном и том же классе, не могут иметь подписи, отличающиеся только параметрами ref и out.
Параметры типа метода находятся в области всего объявления метода, и могут использоваться для формирования типов в этой области в типе возвращаемого значения, теле метода и в предложениях ограничений типов параметров, но не в атрибутах.
Все формальные параметры и параметры типов должны иметь разные имена.
Параметры метода
Параметры метода, если они имеются, объявляются списком формальных параметров метода.
formal-parameter-list:
fixed-parameters
fixed-parameters , parameter-array
parameter-array
fixed-parameters:
fixed-parameter
fixed-parameters , fixed-parameter
fixed-parameter:
attributesopt parameter-modifieropt type identifier default-argumentopt
default-argument:
= expression
parameter-modifier:
ref
out
this
parameter-array:
attributesopt params array-type identifier
Список формальных параметров состоит из одного или более параметров, разделенных запятыми, из которых только последний может быть массивом параметров.
Фиксированный параметр состоит из необязательного набора атрибутов (§17), необязательного модификатора ref, out или this, типа, идентификатора и необязательного аргумента по умолчанию. Каждый фиксированный параметр объявляет параметр данного типа с данным именем. Модификатор this определяет метод как метод расширения и допускается только в первом параметре статического метода. Методы расширения описаны далее в §10.6.9.
Фиксированный параметр с аргументом по умолчанию называется необязательным параметром, в то время как фиксированный параметр без аргумента по умолчанию является обязательным параметром. Обязательный параметр не может стоять после необязательного параметра в списке формальных параметров.
Параметры ref и out не могут иметь аргумент по умолчанию. Выражением в аргументе по умолчанию должно быть одно из следующих:
· константное выражение;
· выражение вида new S(), где S является типом значения;
· выражение вида default(S), где S является типом значения;
Выражение должно неявно преобразовываться идентификацией преобразования, которое может иметь значение NULL, в тип параметра.
Если необязательные параметры встречаются в определяющем объявлении разделяемого метода (§10.2.7), в явной реализации интерфейса члена интерфейса (§13.4.1) или в объявлении индексатора единственного параметра (§10.9), компилятор должен создать предупреждение, поскольку эти члены никогда не могут вызываться способом, в котором аргументы могут отсутствовать.
Массив параметров состоит из необязательного набора атрибутов (§17), модификатора params, типа массива и идентификатора. Массив параметров объявляет единственный параметр заданного типа массива с данным именем. Тип массива массива параметров должен быть типом одномерного массива (§12.1). При вызове метода массив параметров допускает указание либо одного аргумента с данным типом массива, либо ноль или более аргументов типа элемента массива. Параметры массивов описаны далее в §10.6.1.4.
Массив параметров может присутствовать после необязательного параметра, но не может иметь значение по умолчанию – отсутствие аргументов для массива параметров приведет вместо этого к созданию пустого массива.
В двух следующих примерах демонстрируются разные виды параметров.
public void M(
ref int i,
decimal d,
bool b = false,
bool? n = false,
string s = "Hello",
object o = null,
T t = default(T),
params int[] a
) { }
В списке формальных параметров для M i является обязательным параметром ref, d является обязательным параметром по значению, b, s, o и t являются необязательными параметрами по значению, и a является массивом параметров.
Объявление метода создает отдельную область объявлений для параметров, параметров типов и локальных переменных. Имена вводятся в эту область объявлений списком параметров типов и списком формальных параметров метода, и объявлениями локальных переменных в блоке метода. Одно и то же имя для двух членов области объявлений метода является ошибкой. Содержание элементов с одним и тем же именем в области объявлений метода и в области объявлений локальных переменных вложенной области объявлений является ошибкой.
Вызов метода (§7.6.5.1) создает определенную для этого вызова копию формальных параметров и локальных переменных метода, а список аргументов вызова присваивает значения или ссылки на переменные вновь созданным формальным параметрам. Внутри блока метода к формальным параметрам можно обращаться по их идентификаторам в выражениях простого имени (§7.6.2).
Есть четыре вида формальных параметров:
· параметры по значению, объявляемые без модификаторов;
· параметры по ссылке, объявляемые с модификатором ref;
· выходные параметры, объявляемые с модификатором out;
· массивы параметров, объявляемые с модификатором params.
Как описано в §3.6, модификаторы ref и out являются частью подписи метода, а модификатор params – нет.
Параметры по значению
Параметр, объявленный без модификаторов, является параметром по значению. Параметр по значению соответствует локальной переменной, получающей свое начальное значение из соответствующего аргумента, предоставленного во время вызова метода.
Если формальный параметр является параметром по значению, соответствующий аргумент в вызове метода должен быть выражением, неявно преобразуемым (§6.1) в тип формального параметра.
Методу разрешено присваивать новые значения параметру по значению. Такие присваивания влияют только на локальное место хранения, представленное параметром по значению – они не влияют на фактический аргумент, заданный при вызове метода.
Параметры ссылок
Параметр, объявленный с модификатором ref, является параметром по ссылке. В отличие от параметра по значению, параметр по ссылке не создает новое место хранения. Вместо этого параметр по ссылке представляет то же самое место хранения, что и переменная, заданная в качестве аргумента при вызове метода.
Если формальный параметр является параметром по ссылке, соответствующий аргумент в вызове метода должен состоять из зарезервированного слова ref, за которым следует ссылка на переменную (§5.3.3) того же типа, что и формальный параметр. Переменная должна быть определенно назначенной до того, как ее можно будет передать в качестве параметра по ссылке.
Внутри метода параметр по ссылке всегда считается определенно назначенным.
У метода, объявленного как итератор (§10.14), не может быть параметров по ссылке.
Пример:
using System;
class Test
{
static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}
static void Main() {
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine("i = {0}, j = {1}", i, j);
}
}
производятся следующие выходные данные
i = 2, j = 1
Для вызова метода Swap в классе Main, x представляет i, а y представляет j. Таким образом, результатом вызова является обмен значений i и j.
В методе, принимающем параметры по ссылке, несколько имен могут представлять одно и то же место хранения. В этом примере
class A
{
string s;
void F(ref string a, ref string b) {
s = "One";
a = "Two";
b = "Three";
}
void G() {
F(ref s, ref s);
}
}
вызов F из G передает ссылку на s одновременно для a и b. Таким образом, при этом вызове все имена s, a и b ссылаются на одно и то же место хранения, а все три присваивания изменяют поле экземпляра s.
Выходные параметры
Параметр, объявленный с модификатором out, является выходным параметром. Подобно параметру по ссылке, выходной параметр не создает новое место хранения. Вместо этого выходной параметр представляет то же самое место хранения, что и переменная, заданная в качестве аргумента при вызове метода.
Если формальный параметр является выходным параметром, соответствующий аргумент в вызове метода должен состоять из зарезервированного слова out, за которым следует ссылка на переменную (§5.3.3) того же типа, что и формальный параметр. Переменной не требуется быть определенно назначенной, прежде чем ее можно будет передать в качестве выходного параметра, но вслед за вызовом, в котором переменная была передана в качестве выходного параметра, эта переменная считается определенно назначенной.
Внутри метода, точно так же, как и локальная переменная, выходной параметр вначале считается неназначенным, и должен быть определенно назначен до использования его значения.
Каждый выходной параметр метода должен быть определенно назначен до возврата из метода.
Метод, объявленный как частичный (§10.2.7) или как итератор (§10.14), не может иметь выходные параметры.
Выходные параметры обычно используются в методах, создающих несколько возвращаемых значений. Пример:
using System;
class Test
{
static void SplitPath(string path, out string dir, out string name) {
int i = path.Length;
while (i > 0) {
char ch = path[i – 1];
if (ch == '\\' || ch == '/' || ch == ':') break;
i--;
}
dir = path.Substring(0, i);
name = path.Substring(i);
}
static void Main() {
string dir, name;
SplitPath("c:\\Windows\\System\\hello.txt", out dir, out name);
Console.WriteLine(dir);
Console.WriteLine(name);
}
}
Далее показан вывод для вышеуказанного примера.
c:\Windows\System\
hello.txt
Обратите внимание, что переменным dir и name может быть не сделано присваивание до их передачи в SplitPath, но они считаются определенно назначенными вслед за вызовом.
Массивыпараметров
Параметр, объявленный с модификатором params, является массивом параметров. Если список формальных параметров включает массив параметров, тот должен быть последним параметром в списке и должен иметь тип одномерного массива. Например, типы string[] и string[][] могут использоваться в качестве типа массива параметров, а тип string[,] — не может. Невозможно объединить модификатор params с модификаторами ref и out.
Массив параметров позволяет указывать аргументы одним из двух способов при вызове метода:
· Аргумент, заданный для массива параметров, может быть единственным выражением, неявно преобразуемым (§6.1) в тип массива параметров. В этом случае использование массива параметров не отличается от использования параметра значения.
· Либо в вызове могут быть заданы нуль или несколько аргументов для массива параметров, где каждый аргумент является выражением, неявно преобразуемым (§6.1) в тип элемента массива параметров. В этом случае при вызове создается экземпляр типа массива параметров с длиной, соответствующей числу аргументов, происходит инициализация элементов экземпляра массива с помощью указанных значений аргументов и в качестве фактического аргумента используется этот только что созданный экземпляр массива.
Кроме разрешения переменного числа аргументов при вызове, массив параметров совершенно эквивалентен параметру по значению (§10.6.1.1) того же типа.
Пример:
using System;
class Test
{
static void F(params int[] args) {
Console.Write("Array contains {0} elements:", args.Length);
foreach (int i in args)
Console.Write(" {0}", i);
Console.WriteLine();
}
static void Main() {
int[] arr = {1, 2, 3};
F(arr);
F(10, 20, 30, 40);
F();
}
}
производятся следующие выходные данные
Array contains 3 elements: 1 2 3
Array contains 4 elements: 10 20 30 40
Array contains 0 elements:
При первом вызове F просто передает массив a в качестве параметра по значению. При втором вызове F автоматически создает четырехэлементный массив int[] с заданными значениями элементов и передает этот массив в качестве параметра по значению. Аналогично, третий вызов F создает массив из нуля элементов int[] и передает этот экземпляр в качестве параметра по значению. Второй и третий вызовы полностью эквивалентны следующему коду:
F(new int[] {10, 20, 30, 40});
F(new int[] {});
При выполнении разрешения перегрузки метод с массивом параметров может применяться либо в его обычном виде, либо в расширенном виде (§7.5.3.1). Расширенный вид метода доступен, если только неприменим обычный вид, и если только в том же типе уже не объявлен применимый метод с такой же сигнатурой, как у расширенного вида.
Пример:
using System;
class Test
{
static void F(params object[] a) {
Console.WriteLine("F(object[])");
}
static void F() {
Console.WriteLine("F()");
}
static void F(object a0, object a1) {
Console.WriteLine("F(object,object)");
}
static void Main() {
F();
F(1);
F(1, 2);
F(1, 2, 3);
F(1, 2, 3, 4);
}
}
производятся следующие выходные данные
F();
F(object[]);
F(object,object);
F(object[]);
F(object[]);
В этом примере два из возможных расширенных видов метода с массивом параметров уже включены в класс как обычные методы. Поэтому эти расширенные виды не рассматриваются при выполнении разрешения перегрузки, а первый и третий вызовы метода таким образом выбирают обычные методы. Если в классе объявляют метод с массивом параметров, нередко включают и некоторые из расширенных видов в качестве обычных методов. При этом становится возможным избежать выделения экземпляра массива, которое происходит при вызове расширенного вида метода с массивом параметров.
Если массив параметров имеет тип object[], возникает потенциальная неоднозначность между нормальной и расширенной формами метода для одного параметра типа object. Причиной неоднозначности является то, что object[] сам может неявно преобразовываться в тип object. Неоднозначность не ведет к возникновению проблем и при необходимости может быть разрешена с помощью приведения.
Пример:
using System;
class Test
{
static void F(params object[] args) {
foreach (object o in args) {
Console.Write(o.GetType().FullName);
Console.Write(" ");
}
Console.WriteLine();
}
static void Main() {
object[] a = {1, "Hello", 123.456};
object o = a;
F(a);
F((object)a);
F(o);
F((object[])o);
}
}
производятся следующие выходные данные
System.Int32 System.String System.Double
System.Object[]
System.Object[]
System.Int32 System.String System.Double
При первом и последнем вызове функции F применима нормальная форма F, поскольку существует неявное преобразование из типа аргумента к типу параметра (оба имеют тип object[]). Таким образом, в результате разрешения перегрузки выбирается нормальная форма F, а аргумент передается как регулярный параметр значения. Во втором и в третье вызовах обычная форма F неприменима, поскольку не существует неявного преобразования из типа аргумента в тип параметра (тип object не может быть неявно преобразован в тип object[]). Однако в этом случае применима расширенная форма F, которая выбирается в результате разрешения перегрузки. В результате вызовом создается одноэлементный массив object[], и единственный элемент массива инициализируется данным значением аргумента (который сам является ссылкой на object[]).