Есть например вот такая функция
void Foo (int х = 23)
{
Console.WriteLine (х);
}
который имеет необязательный параметр x. Я всегда читал, что для параметров и локальных переменных в функции начисляется память, то есть строится так называемый stack тогда, когда вызывается функция.
Объясните, в таком случае, где хранится значение переменной x.
Стек создается не при вызрве функции, а при создании потока исполнения (execution thread). При вызове функции в нем выделяется и инициализируется память под локальные переменные и формальные аргументы (иногда это делается через регистры, но IL умеет только через стек).
Стек потока - это область памяти с механизмом доступа. Значением необязательного параметра всегда является констата (значение, вычисляемое при компиляции), поэтому наверняка для CLR она хранится там же, где и остальные константы.
Смотрите, всё просто. На самом деле, необязательные параметры обрабатываются не на уровне вызываемой функции, а на уровне вызывающей.
Что это означает? А вот что. Для объявления
void Foo(int х = 23)
{
Console.WriteLine(х);
}
компилятор не генерирует две функции:
void Foo(int х)
{
Console.WriteLine(х);
}
void Foo()
{
int х = 23;
Console.WriteLine(х);
}
Он в реальности генерирует лишь одну функцию
void Foo(int х)
{
Console.WriteLine(х);
}
А вот вызов Foo() при этом компилятором переписывается так, как будто вы написали Foo(23)! Таким образом, константа 23 вставляется в вызывающий код на этапе компиляции. И хранится он там же, где и хранилась бы при явном вызове Foo(23).
Отсюда такое следствие. Допустим, ваша функция Foo определена в сборке A, а сборка B вызывает Foo(). Затем, вы меняете в исходнике значение по умолчанию с 23 на 0, и компилируете снова сборку A, но не сборку B. Поскольку константа 23 «вкомпилирована» в сборку B, то при запуске функция Foo будет фактически с параметром x = 23, а не 0.
Источник информации: Optional argument corner cases, part three.
Хочу добавить к ответу цитату из Рихтера clr via c#
При вызове метода извне модуля изменение значения параметров по умолчанию является потенциально опасным. Вызывающая сторона использует значение по умолчанию в процессе работы. Если изменить его и не перекомпилировать код, содержащий вызов, в вызываемый метод будет передано прежнее значение. В качестве индикатора поведения можно использовать значение по умолчанию 0 или null. В результате исчезает необходимость повторной компиляции кода вызывающей стороны. Вот пример:
// Не делайте так:
private static String MakePath(String filename = "Untitled") {
return String.Format(@"C:\{0}.txt", filename);
}
// Используйте следующее решение:
private static String MakePath(String filename = null) {
// Здесь применяется оператор, поддерживающий
// значение null (??);
return String.Format(@"C:\{0}.txt", filename ?? "Untitled");
}
Сборка персонального компьютера от Artline: умный выбор для современных пользователей