В чем разница между ref и out во время выполнения?

15

C # предоставляет ключевое слово refand outдля аргументов, передаваемых по ссылке. Семантика двух очень похожа. Единственная разница заключается в инициализации переменной:

  • refтребует, чтобы переменная была инициализирована перед передачей функции, outнет.
  • outтребует, чтобы переменная была инициализирована внутри функции, refнет.

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

Из-за этого кто-то может объяснить, почему в C # есть два очень похожих инструмента для столь узких вариантов использования?

Также в MSDN указано, что у них другое поведение во время выполнения:

Хотя ключевые слова ref и out вызывают различное поведение во время выполнения, они не считаются частью сигнатуры метода во время компиляции.

Чем отличается их поведение во время выполнения?

Вывод

Оба ответа выглядят правильно, спасибо вам обоим. Я принял Jmoreno , потому что это более явно.

Габор Ангьял
источник
Я думаю, они оба реализованы так же, как refв C ++; указатели на указатель объекта (или примитив) в вопросе. т.е. Int32.TryParse(myStr, out myInt)(C #) «выполняется» так же, как int32_tryParse(myStr, &myInt)(C); единственное отличие заключается в том, что компилятор налагает некоторые ограничения на предотвращение ошибок. (Я не собираюсь публиковать это как ответ, потому что я могу ошибаться из-за того, как это работает за кулисами, но я думаю, что это работает [потому что это имеет смысл])
Коул Джонсон,

Ответы:

10

В то время, когда был задан этот вопрос, статья MSDN была немного неверной (с тех пор она была исправлена ). Вместо «вызывать другое поведение» оно должно быть «требует другого поведения».

В частности, компилятор обеспечивает разные требования для двух ключевых слов, хотя для включения поведения используется один и тот же механизм (IL).

jmoreno
источник
Поэтому я понимаю, что компилятор проверяет такие вещи, как разыменование outпараметра или не присваивание refпараметра, но IL, который он выдает, в любом случае является просто ссылкой. Это верно?
Андрей
Кстати, можно было бы сказать , что это из - за того , что out Tи ref Tоказаны одинаково на других языках CLI , таких как VB.NET или C ++ / CLI.
ACH
@AndrewPiliser: правильно. Предполагая, что код компилируется в любом случае (вы не пытаетесь разыменовать, когда не должны), IL будет идентичным.
Jmoreno
4

Вот интересная рецензия на тему, которая может ответить на ваш вопрос:

http://www.dotnetperls.com/ref

достопримечательность:

«Разница между ref и out не в Common Language Runtime, а в самом языке C #».

Обновить:

Старший разработчик моей работы только что подтвердил ответ @ jmoreno, поэтому обязательно прочтите его !.

Дэн Болье
источник
Так что, если я правильно понимаю, на уровне IL нет различий, что подразумевает, что они не могут иметь различное поведение во время выполнения. Это противоречило бы тому, что говорит MSDN. Интересная статья тем не менее!
Габор Ангиал
@ GáborAngyal У тебя случайно нет ссылки на статью MSDN? Было бы неплохо иметь это здесь, если мнения расходятся
Дэн Болье
Вот, пожалуйста : msdn.microsoft.com/en-gb/library/t3c3bfhx.aspx , но это было в моем вопросе все время :)
Gábor Angyal
1
Что stringобщего со всем этим?
Роберт Харви
Это просто парафраз из статьи. Обновлено для удаления второй строки ..
Дэн Болье
2

Время выполнения ? Абсолютно нет. Вы не можете перегружать метод с одинаковыми параметрами, отличающимися только ключевым словом ref или out.

Попробуйте скомпилировать это, и вы получите ошибку компиляции «Метод с такой же подписью уже объявлен»:

    private class MyClass
    {
        private void DoSomething(out int param)
        {
        }
        private void DoSomething(ref int param)
        {
        }
    }

Чтобы ответить на этот вопрос: «... почему в C # есть два очень похожих инструмента для столь узких вариантов использования?»

С точки зрения читабельности кода и API существует огромная разница. Как потребитель API я знаю, что при использовании «out» API не зависит от параметра out. Как разработчик API, я предпочитаю «out» и использую «ref» только тогда, когда это абсолютно НЕОБХОДИМО! Смотрите эту ссылку для большой дискуссии:

/programming/1516876/when-to-use-ref-vs-out

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

    private class MyClass
    {
        internal void DoSomething(out int param)
        {
            param = 0;
        }
    }

00000000 толчок EBP
00000001 мов EBP, ESP
00000003 толчок еди
00000004 толчок еси
00000005 толчок EBX
00000006 суб особ, 34h
00000009 XOR EAX, EAX
0000000b мов DWORD PTR [EBP-10h], EAX
0000000e мов DWORD PTR [EBP-1Ch], EAX
00000011 mov dword ptr [ebp-3Ch], ecx
00000014 mov dword ptr [ebp-40h], edx
00000017 cmp dword ptr ds: [008B1710h], 0
0000001e je 00000025
00000020 вызов 6E6B601E
00000025 nop
param = 0;
00000026 mov eax, dword ptr [ebp-40h]
00000029 xor edx, edx
0000002b mov dword ptr [eax], edx
}
0000002d nop
0000002e lea esp, [ebp-0Ch]
00000031 pop ebx
00000032 pop esi
00000033 pop
edi 00000034 pop ebp
00000035 ret

Я правильно читаю сборку?

Shmoken
источник
То, что вы пишете, правильно, но, пожалуйста, обратите внимание, что это не является очевидным следствием отсутствия разницы во времени выполнения, поскольку эта перегрузка недопустима.
Габор Angyal
Ааа - хорошая мысль! Может кто-нибудь разобрать приведенный выше код и доказать, что я прав или нет? Я ужасно читаю сборку, но мне интересно знать.
Шмокен
Хорошо - я разобрал код, используя "out" и "ref", и я не вижу никакой разницы для моего метода DoSomething:
Shmoken