C# необязательный параметр

338
23 декабря 2016, 11:27

Есть например вот такая функция

void Foo (int х = 23) 
{ 
  Console.WriteLine (х);
}  

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

Answer 1

Стек создается не при вызрве функции, а при создании потока исполнения (execution thread). При вызове функции в нем выделяется и инициализируется память под локальные переменные и формальные аргументы (иногда это делается через регистры, но IL умеет только через стек).

Стек потока - это область памяти с механизмом доступа. Значением необязательного параметра всегда является констата (значение, вычисляемое при компиляции), поэтому наверняка для CLR она хранится там же, где и остальные константы.

Answer 2

Смотрите, всё просто. На самом деле, необязательные параметры обрабатываются не на уровне вызываемой функции, а на уровне вызывающей.

Что это означает? А вот что. Для объявления

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.

Answer 3

Хочу добавить к ответу цитату из Рихтера 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");
}
READ ALSO
Формула Беллара

Формула Беллара

Здравствуйте, есть формула Беллара которая должна возвращать n-й разряд числа пи в двоичном представлении:

399
Как выделить узел дерева правым кликом мышки?

Как выделить узел дерева правым кликом мышки?

При правом клике мышки узел должен выделяться как и при левом клике

299
Как определить какой узел был выбран при правом клике мышки по узлу дерева?

Как определить какой узел был выбран при правом клике мышки по узлу дерева?

Это событие возвращает object sender и MouseButtonEventArgs eЧерез e

272