Вычисление списков аргументов во время выполнения
При обработке вызова функции-члена во время выполнения (§7.5.4) выражения или ссылки на переменные списка аргументов вычисляются слева направо в следующем порядке.
· Для параметра значения вычисляется выражение аргумента и выполняется неявное преобразование (§6.1) в соответствующий тип параметра. Результат становится исходным значением параметра значения при вызове функции-члена.
· Для параметра ссылки или вывода вычисляется ссылка на переменную и получившееся расположение в памяти становится расположением, которое представляется параметром при вызове функции-члена. Если ссылка на переменную, предоставленная в виде параметра ссылки или вывода, является элементом массива ссылочного типа, во время выполнения проверяется, совпадает ли тип элемента массива с типом параметра. В случае неудачного завершения проверки возникает исключение System.ArrayTypeMismatchException.
В методах, индексаторах и конструкторах экземпляров самый правый параметр может объявляться массивом параметров (§10.6.1.4). Такие функции-члены вызываются либо в нормальной форме, либо в расширенной в зависимости от того, какая из форм применима (§7.5.3.1):
· Когда функция-член с массивом параметров вызывается в нормальной форме, аргумент массива параметров должен быть одним выражением с типом, который можно неявно преобразовать (§6.1) в тип массива параметров. В этом случае использование массива параметров не отличается от использования параметра значения.
· Когда функция-член с массивом параметров вызывается в расширенной форме, при вызове необходимо указать любое число аргументов для массива параметров, где каждый аргумент представляет собой выражение с типом, который можно неявно преобразовать (§6.1) в тип элементов массива параметров. В этом случае при вызове создается экземпляр типа массива параметров с длиной, соответствующей числу аргументов, происходит инициализация элементов экземпляра массива с помощью указанных значений аргументов и в качестве фактического аргумента используется этот только что созданный экземпляр массива.
Выражения списка аргументов всегда вычисляются в порядке их записи. Таким образом, в примере
class Test
{
static void F(int x, int y = -1, int z = -2) {
System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z);
}
static void Main() {
int i = 0;
F(i++, i++, i++);
F(z: i++, x: i++);
}
}
производятся следующие выходные данные
x = 0, y = 1, z = 2
x = 4, y = -1, z = 3
По правилам ковариации массива (§12.5) значение массива типа A[] может быть ссылкой на экземпляр массива типа B[], если существует неявное преобразование ссылочного типа из B в A. Согласно этим правилам, когда элемент массива ссылочного типа передается в качестве параметра ссылки или вывода, во время выполнения необходимо убедиться, что фактический тип элементов массива идентичен типу параметра. В этом примере
class Test
{
static void F(ref object x) {...}
static void Main() {
object[] a = new object[10];
object[] b = new string[10];
F(ref a[0]); // Ok
F(ref b[1]); // ArrayTypeMismatchException
}
}
второй вызов F приводит к исключению System.ArrayTypeMismatchException, поскольку фактический тип элемента для b — string, а не object.
Когда функция-член с массивом параметров вызывается в расширенной форме, вызов обрабатывается точно так же, как если бы для расширенных параметров было указано выражение создания массива с инициализатором массива (§7.6.10.4). Например, при объявлении
void F(int x, int y, params object[] args);
следующие вызовы расширенной формы метода
F(10, 20);
F(10, 20, 30, 40);
F(10, 20, 1, "hello", 3.0);
в точности соответствуют
F(10, 20, new object[] {});
F(10, 20, new object[] {30, 40});
F(10, 20, new object[] {1, "hello", 3.0});
В частности, обратите внимание, что когда в массиве параметров аргументы не передаются, создается пустой массив.
Когда аргументы в функции-члене с соответствующими необязательными параметрами пропускаются, выполняется неявная передача аргументов по умолчанию объявления функции-члена. Так как они всегда постоянны, их вычисление не повлияет на порядок вычисления остальных аргументов.
Вывод типа
При вызове универсального метода без указания аргументов типа процесс вывода типа пытается определить аргументы типа для вызова. Наличие вывода типа позволяет использовать более удобный синтаксис для вызова универсального метода и позволяет разработчику избегать указания избыточных данных о типе. Например, при объявлении метода
class Chooser
{
static Random rand = new Random();
public static T Choose<T>(T first, T second) {
return (rand.Next(2) == 0)? first: second;
}
}
можно вызвать метод Choose без явного указания аргумента типа:
int i = Chooser.Choose(5, 213); // Calls Choose<int>
string s = Chooser.Choose("foo", "bar"); // Calls Choose<string>
С помощью вывода типа аргументы типа int и string определяются из аргументов метода.
Вывод типа происходит при обработке вызова метода во время компиляции (§7.6.5.1) до этапа разрешения перегрузки для вызова. Когда в вызове метода указывается конкретная группа методов и не указываются аргументы типа, вывод типа применяется к каждому универсальному методу в группе методов. Если вывод типа завершается успехом, то полученные аргументы типа используются для определения типов аргументов для последующего разрешения перегрузки. Если при разрешении перегрузки выбирается вызов универсального метода, то при вызове в качестве фактических аргументов типа будут использоваться полученные аргументы. Если вывод типа для конкретного метода завершается неудачно, то такой метод не участвует в разрешении перегрузки. Ошибка при выводе типа сама по себе не приводит к ошибке времени привязки. Однако она часто приводит к ошибке времени привязки, когда при разрешении перегрузки не удается найти применимые методы.
Если предоставленное число аргументов отличается от числа параметров в методе, то вывод типа немедленно завершается ошибкой. Иначе предположим, что у универсального метода имеется следующая сигнатура
Tr M<X1…Xn>(T1 x1 … Tm xm)
При вызове метода в виде M(E1 …Em) задачей вывода типа является найти уникальные аргументы типа S1…Sn для каждого параметра типа X1…Xn, чтобы вызов M<S1…Sn>(E1…Em) оказался действительным.
Во время процесса вывода каждый параметр типа Xi либо фиксирован с определенным типом Si, либо нефиксирован со связанным набором границ. Каждая граница представляет собой некоторый тип T. Изначально каждая переменная типа Xi является нефикисированной с пустым набором границ.
Вывод типа происходит поэтапно. На каждом этапе выполняется попытка вывести аргументы типа для большего числа переменных типа на основании результатов предыдущего этапа. На первом этапе выводятся начальные границы, на втором этапе переменные связываются с определенными типами и выводятся дополнительные границы. Второй этап может потребоваться повторить несколько раз.
Обратите внимание: вывод типа происходит не только при вызове универсального метода. Вывод типа для преобразования групп методов описывается в разделе §7.5.2.13, а поиск самого подходящего общего типа для набора выражений описывается в разделе §7.5.2.14.
Первый этап
Для каждого аргумента метода Ei:
- Если Ei является анонимной функцией, выполняется явный вывод типа параметра (§7.5.2.7) из Ei к Ti.
- В противном случае, если Ei имеет тип U, а xi — параметр значения, выполняется вывод по нижней границе от U до Ti.
- В противном случае, если Ei имеет тип U, а xi является параметром ref или out, выполняется точный вывод из U в Ti.
- В противном случае вывод для этого аргумента не выполняется.
Второй этап
Второй этап продолжается следующим образом.
- Фиксируются все нефиксированные переменные типа Xi, которые не зависят (§7.5.2.5) от каких-либо Xj (§7.5.2.10).
- Если таких переменных типа нет, то фиксируются все нефиксированные переменные типа Xi, для которых верно, что:
- существует по крайней мере одна переменная типа Xj , которая зависит от Xi;
- Xi имеет непустой набор границ.
- Если таких переменных типа не существует и все еще остаются нефиксированные переменные типа, то вывод типа завершается сбоем.
- Иначе, если нефиксированных переменных типа больше не существует, вывод типа завершается успешно.
- Иначе для всех аргументов Ei с соответствующим типом параметра Ti, где выходные типы (§7.5.2.4) содержат нефиксированные переменные типа Xj, а входные типы (§7.5.2.3) не содержат, выполняется вывод выходного типа (§7.5.2.6) от Ei к Ti. После этого второй этап повторяется.
Типы ввода
Если E является группой методов или анонимной функцией с неявным указанием типа, а T является типом делегата или типом дерева выражения, то все типы параметров T являются типами ввода E с типомT.
Типы вывода
Если E является группой методов или анонимной функцией, а T является типом делегата или типом дерева выражения, то типом возвращаемого значения T является тип вывода E с типом T.
Зависимость
Нефиксированная переменная типа Xi непосредственно зависит от нефиксированной переменной типа Xj, если некоторый аргумент Ek с типом Tk Xj оказывается во входном типе Ek с типом Tk, а Xi оказывается в выходном типе Ek с типом Tk.
Xj зависит от Xi, если Xj непосредственно зависит от Xi или если Xi непосредственно зависит от Xk и Xk зависит от Xj. Таким образом, отношение зависит от является транзитивным, но не является рефлексивным замыканием отношения непосредственно зависит от.
Вывод типа вывода
Вывод типа вывода выполняется из выражения E в тип T следующим образом:
· Если E является анонимной функцией с полученным типом возвращаемого значения U (§7.5.2.12), а T является типом делегата или типом дерева выражения с типом возвращаемого значения Tb, то выполняется вывод по нижней границе (§7.5.2.9) из U к Tb.
· Иначе, если E является группой методов, а T является типом делегата или типом дерева выражений с типами параметров T1…Tk и типом возвращаемого значения Tb и разрешение перегрузки E с типами T1…Tk дает один метод с типом возвращаемого значения U, то выполняется вывод по нижней границе из U к Tb.
· Иначе, если E является выражением с типом U, то выполняется вывод по нижней границе из U к T.
· Иначе вывод не производится.