Qual é a diferença entre ref e out em tempo de execução?

15

C # fornece a refea outpalavra-chave para argumentos fazer para ser passado por referência. A semântica dos dois é muito semelhante. A única diferença está na inicialização da variável flaged:

  • refrequer que a variável seja inicializada antes de passá-la para a função, outnão.
  • outrequer que a variável seja inicializada dentro da função, refnão.

Os casos de uso dessas duas palavras-chave também são quase os mesmos, e seu uso muito frequente é considerado um cheiro de código (embora existam casos de uso válidos, como os padrões TryParsee TryGetValue).

Por isso, alguém poderia explicar, por que existem duas ferramentas muito semelhantes em C # para casos de uso tão restritos?

Além disso, no MSDN , afirma-se que eles têm um comportamento de tempo de execução diferente:

Embora as palavras-chave ref e out causem um comportamento de tempo de execução diferente, elas não são consideradas parte da assinatura do método no tempo de compilação.

Qual a diferença entre o comportamento em tempo de execução?

Conclusão

Ambas as respostas parecem corretas, obrigado a ambos. Aceitei o jmoreno porque é mais explícito.

Gábor Angyal
fonte
Meu palpite é que ambos são implementados da mesma maneira que refem C ++; para ponteiros para o ponteiro de objeto (ou primitivo) em questão. ie Int32.TryParse(myStr, out myInt)(C #) é "executado" da mesma maneira que int32_tryParse(myStr, &myInt)(C) seria; a única diferença são algumas restrições impostas pelo compilador para evitar bugs. (Eu não vou postar isso como uma resposta porque eu posso estar errado sobre como isso funciona nos bastidores, mas é assim que eu imagino ele funciona [porque faz sentido])
Cole Johnson

Respostas:

10

No momento em que essa pergunta foi feita, o artigo do MSDN estava sutilmente incorreto ( já foi corrigido ). Em vez de "causar comportamento diferente", deveria ser "requer comportamento diferente".

Em particular, o compilador impõe requisitos diferentes para as duas palavras-chave, mesmo que o mesmo mecanismo (IL) seja usado para habilitar o comportamento.

jmoreno
fonte
Portanto, meu entendimento é que o compilador verifica coisas como desreferenciar um outparâmetro ou não atribuir um refparâmetro, mas a IL que ele emite é apenas uma referência de qualquer maneira. Isso está correto?
18715 Andrew Andrew
Btw, se poderia dizer isso a partir do fato de que out Te ref Tsão prestados de forma idêntica em outros idiomas CLI como VB.Net ou C ++ / CLI.
ACH
@AndrewPiliser: correto. Supondo que o código seja compilado de qualquer maneira (você não tenta desreferenciar quando não deveria), a IL seria idêntica.
jmoreno
4

Aqui está um artigo interessante sobre o tópico que pode responder à sua pergunta:

http://www.dotnetperls.com/ref

ponto de interesse:

"A diferença entre ref e out não está no Common Language Runtime, mas está na própria linguagem C #."

atualizar:

o desenvolvedor sênior do meu trabalho corroborou a resposta de @ jmoreno, portanto, leia-a !.

Dan Beaulieu
fonte
Portanto, se eu entendi corretamente, não há diferença no nível de IL, o que implica que eles não podem ter um comportamento de tempo de execução diferente. Isso contradiz o que o MSDN diz. Artigo interessante no entanto!
Gábor Angyal
@ GáborAngyal, por acaso você não tem um link para o artigo do MSDN, por acaso? Pode ser bom ter essa aqui se opiniões diferem
Dan Beaulieu
Aqui vai: msdn.microsoft.com/en-gb/library/t3c3bfhx.aspx , mas foi na minha pergunta o tempo todo :)
Gábor Angyal
1
O que stringtem a ver com tudo isso?
Robert Harvey
É apenas uma paráfrase do artigo. Atualizado para remover a segunda linha ..
Dan Beaulieu
2

Tempo de execução ? Absolutamente nenhum. Você não pode sobrecarregar um método com os mesmos parâmetros que diferem apenas pela palavra-chave ref ou out.

Tente compilar isso e você receberá um erro de compilação "O método com a mesma assinatura já está declarado":

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

Para responder a esta pergunta: "... por que existem duas ferramentas muito semelhantes em C # para casos de uso tão restritos?"

Do ponto de vista da legibilidade do código e da API, há uma enorme diferença. Como consumidor da API, sei que quando "out" é usado, a API não depende do parâmetro out. Como desenvolvedor de API, eu prefiro o "out" e uso o "ref" somente quando absolutamente (RARAMENTE!) Necessário. Veja esta referência para uma ótima discussão:

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

Informações de suporte: compilei o método a seguir e desmontei. Usei as palavras-chave ref e out (neste exemplo), mas o código do assembly não mudou, exceto por uma referência de endereço, como seria de esperar:

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

00000000 push ebp
00000001 mov ebp, esp
00000003 push edi
00000004 push esi
00000005 push ebx
00000006 subesp, 34h
00000009 xor eax, eax
0000000b mov dword ptr [ebp-10h], eax
0000000e mov 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 chamada 6E6B601E
00000025 nop
param = 0;
00000026 mov eax, dword ptr [ebp-40h]
00000029 xor edx, edx
0000002b mov dword ptr [eax], edx
}
0000002d de
0000002e lea esp, [ebp-0Ch]
00000031 pop ebx
00000032 pop esi
00000033 pop edi 00000033 pop edi
00000034 pop ebp
00000035 ret

Estou lendo a montagem corretamente?

Shmoken
fonte
O que você escreve está correto, mas observe que não é uma implicação evidente que não há diferença de tempo de execução porque essa sobrecarga não é permitida.
Gábor Angyal 12/02/2015
Ahh - bom ponto! Alguém pode desmontar o código acima e provar que estou certo ou errado? Sou péssimo em ler assembléias, mas curioso para saber.
Shmoken 13/02/2015
Ok - desmontei o código usando "out" e "ref" e não vejo diferença para o meu método DoSomething:
Shmoken