Подтвердить что ты не робот

Различия между тем, как С# и VB обрабатывают именованные параметры?

Теперь, когда С# поддерживает именованные параметры, я проверял, был ли он реализован так же, как это сделал VB, и обнаружил, что есть небольшая разница. Возьмем, например, библиотечную функцию:

public static void Foo(string a, string b)
{
    Console.WriteLine(string.Format("a: {0}, b: {1}", a, b));
}

В С#, если вы вызываете это следующим образом:

Foo(a: "a", b: "b");

Компилятор создает следующие инструкции IL:

.locals init (
    [0] string CS$0$0000,
    [1] string CS$0$0001)
L_0000: nop 
L_0001: ldstr "a"
L_0006: stloc.0 
L_0007: ldstr "b"
L_000c: stloc.1 
L_000d: ldloc.0 
L_000e: ldloc.1 
L_000f: call void [TestLibrary]TestLibrary.Test::Foo(string, string)
L_0014: nop 
L_0015: ret 

Что означает следующий код С#:

string CS$0$0000 = "a";
string CS$0$0001 = "b";
Test.Foo(CS$0$0000, CS$0$0001);

В VB, если вы вызываете это так:

Foo(a:="a", b:="b")

Компилятор создает следующие инструкции IL:

L_0000: nop 
L_0001: ldstr "a"
L_0006: ldstr "b"
L_000b: call void [TestLibrary]TestLibrary.Test::Foo(string, string)
L_0010: nop 
L_0011: nop 
L_0012: ret 

Что означает следующий код VB:

Foo("a", "b");

То, как VB это требует, требует гораздо меньше вызовов команд, так есть ли какое-то преимущество в том, как С# реализует его? Если вы не используете именованные параметры, С# производит то же, что и VB.


EDIT. Теперь, когда мы определили, что дополнительные инструкции исчезнут в режиме деблокирования, есть ли у них определенная причина присутствовать в режиме отладки? VB действует одинаково в обоих режимах, а С# не вставляет дополнительные инструкции при вызове метода обычно без именованных параметров (в том числе при использовании необязательных параметров).

4b9b3361

Ответ 1

есть ли конкретная причина для их присутствия в режиме отладки?

Разница между:

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

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

Создание сборщика мусора менее агрессивным часто помогает в сценариях отладки.

Подразумеваемый вопрос:

Почему разница между С# и VB?

Компиляторы С# и VB были написаны разными людьми, которые делали разные варианты о том, как работают их соответствующие генераторы кода.

UPDATE: Re: ваш комментарий

В компиляторе С# неоптимизированное генерация IL по существу имеет ту же структуру, что и внутреннее представление этой функции. Когда мы видим именованный аргумент:

M(y : Q(), x : R());

где метод, скажем,

void M(int x, int y) { }

мы представляем это внутренне, как если бы вы написали

int ytemp = Q();
int xtemp = R();
M(xtemp, ytemp);

Поскольку мы хотим сохранить левую-правую оценку побочных эффектов Q и R. Это разумное внутреннее представление, и когда мы его кодируем в неоптимизированном режиме, мы просто кодируем код непосредственно из внутреннее представление практически без изменений.

Когда мы запускаем оптимизатор, мы обнаруживаем всевозможные вещи - например, тот факт, что никто не использует эти невидимые локальные переменные ни для чего. Затем мы можем устранить локали из кодегена.

Я очень мало знаю о внутреннем представлении VB; Я не работал с компилятором VB с 1995 года, и я слышал, что за последние пятнадцать лет он мог немного измениться. Я бы предположил, что они делают что-то подобное, но я не знаю деталей того, как они представляют именованные параметры или как их генератор кода работает с ними.

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

Ответ 2

Компилятор создает следующий код С#:

Нет - компилятор создает IL, который вы затем переводите на С#. Любое сходство с кодом С# является чисто случайным (и не все созданные ИЛ могут быть записаны как С#). Присутствие всех этих "nop" говорит мне, что вы находитесь в режиме "отладки". Я бы повторил в режиме "выпуска" - это может иметь большое значение для этих вещей.

Я выпустил его в режиме выпуска, используя:

static void Main()
{
    Foo(a: "a", b: "b");
}

Дарение:

.method private hidebysig static void Main() cil managed
{
    .entrypoint
    .maxstack 8
    L_0000: ldstr "a"
    L_0005: ldstr "b"
    L_000a: call void ConsoleApplication1.Program::Foo(string, string)
    L_000f: ret 
}

То же самое.