Quando usar em vs ref vs out


383

Alguém me perguntou outro dia quando deveria usar a palavra-chave parâmetro em outvez de ref. Enquanto eu (acho) entendo a diferença entre as palavras ref- outchave e (que já foi perguntada antes ) e a melhor explicação parece ser essa ref== ine out, quais são alguns exemplos (hipotéticos ou de código) em que eu sempre devo usar oute não ref.

Como refé mais geral, por que você deseja usar out? É apenas açúcar sintático?


18
Uma variável transmitida usando outnão pode ser lida antes de ser atribuída. refnão tem essa restrição. Então tem isso.
Corey Ogburn

17
Em resumo, refé para entrada / saída, enquanto outé um parâmetro somente para saída.
Tim S.

3
O que exatamente você não entende?
tnw

4
Também outé necessário atribuir variáveis ​​na função.
Corey Ogburn

Obrigado Corey. Mas eu já não sou isso. Meu argumento é que qual é o benefício disso. Na verdade, preciso de um exemplo que mostre um cenário em que possamos usar o parâmetro ref para obter uma funcionalidade que não pode ser alcançada usando o parâmetro out e vice-verso.
Rajbir Singh

Respostas:


399

Você deve usar a outmenos que precise ref.

Faz uma grande diferença quando os dados precisam ser agrupados, por exemplo, para outro processo, o que pode ser caro. Portanto, você deseja evitar a organização do valor inicial quando o método não o utiliza.

Além disso, também mostra ao leitor da declaração ou da chamada se o valor inicial é relevante (e potencialmente preservado) ou jogado fora.

Como uma pequena diferença, um parâmetro out não precisa ser inicializado.

Exemplo para out:

string a, b;
person.GetBothNames(out a, out b);

onde GetBothNames é um método para recuperar dois valores atomicamente, o método não altera o comportamento, independentemente de aeb. Se a chamada for para um servidor no Havaí, copiar os valores iniciais daqui para o Havaí é um desperdício de largura de banda. Um trecho semelhante usando ref:

string a = String.Empty, b = String.Empty;
person.GetBothNames(ref a, ref b);

pode confundir os leitores, porque parece que os valores iniciais de aeb são relevantes (embora o nome do método indique que não são).

Exemplo para ref:

string name = textbox.Text;
bool didModify = validator.SuggestValidName(ref name);

Aqui o valor inicial é relevante para o método.


5
"Esse não é realmente o caso." - você pode explicar melhor o que você quer dizer?
Peterchen

3
Você não deseja usar refpara valores padrão.
C.Evenhuis

155
Para a posteridade: Uma outra diferença que ninguém mais parece ter mencionado, como afirmado aqui ; para um outparâmetro, o método de chamada é necessário para atribuir um valor antes que o método retorne. - você não precisa fazer nada com um parâmetro ref.
Brichins

3
@brichins Consulte a seção 'comentários (adições à comunidade)' no link mencionado por você. É um erro corrigido na documentação do VS 2008.
Bharat Ram V

13
@brichins o método chamado é necessário para atribuir um valor, não o método de chamada. zverev.eugene foi o que foi corrigido na documentação do VS 2008.
Segfault 26/03

72

Use para indicar que o parâmetro não está sendo usado, apenas configure. Isso ajuda o chamador a entender que você está sempre inicializando o parâmetro.

Além disso, ref e out não são apenas para tipos de valor. Eles também permitem redefinir o objeto ao qual um tipo de referência está fazendo referência dentro de um método.


3
1 Eu não sabia que ele pode ser usado para tipos de referência também, resposta clara agradável, graças
Dale

@ brichins: Não, você não pode. outparâmetros são tratados como não atribuídos na entrada da função. Você não poderá inspecionar o valor deles antes de atribuir definitivamente algum valor - não há como usar o valor que o parâmetro tinha quando a função foi chamada.
precisa

Verdade, você não pode acessar o valor antes de uma atribuição interna. Eu estava me referindo ao fato de que o próprio parâmetro pode ser usado posteriormente no método - ele não está bloqueado. Se isso realmente deve ser feito ou não é uma discussão diferente (sobre design); Eu só queria salientar que era possível. Obrigado pelo esclarecimento.
brichins

2
@ ดาว: Pode ser usado com tipos de referência, porque quando você passa um parâmetro de tipo de referência, o que você está passando é o valor da referência e não o próprio objeto. Portanto, ainda é passado por valor.
Tarik

38

Você está correto em que, semanticamente, reffornece a funcionalidade "entrada" e "saída", enquanto outapenas fornece a funcionalidade "saída". Há algumas coisas a considerar:

  1. outrequer que o método que aceita o parâmetro DEVE, em algum momento antes de retornar, atribuir um valor à variável. Você encontra esse padrão em algumas classes de armazenamento de dados de chave / valor Dictionary<K,V>, como , onde você tem funções como TryGetValue. Esta função aceita um outparâmetro que armazena qual será o valor se recuperado. Não faria sentido para o chamador passar um valor para essa função, portanto, outé usado para garantir que algum valor esteja na variável após a chamada, mesmo que não sejam dados "reais" (no caso de TryGetValueonde a chave não está presente).
  2. oute refparâmetros são empacotados de maneira diferente ao lidar com código de interoperabilidade

Além disso, como um aparte, é importante observar que, embora os tipos de referência e os tipos de valores diferam na natureza de seus valores, todas as variáveis ​​em seu aplicativo apontam para um local da memória que contém um valor , mesmo para os tipos de referência. Acontece que, com tipos de referência, o valor contido nesse local da memória é outrolocalização da memória. Quando você passa valores para uma função (ou faz qualquer outra atribuição de variável), o valor dessa variável é copiado na outra variável. Para tipos de valor, isso significa que todo o conteúdo do tipo é copiado. Para tipos de referência, isso significa que o local da memória é copiado. De qualquer forma, ele cria uma cópia dos dados contidos na variável. A única relevância real que isso possui lida com a semântica da atribuição; ao atribuir uma variável ou passar por valor (o padrão), quando uma nova atribuição é feita à variável original (ou nova), ela não afeta a outra variável. No caso de tipos de referência, sim, alterações feitas na instânciaestão disponíveis nos dois lados, mas isso ocorre porque a variável real é apenas um ponteiro para outro local da memória; o conteúdo da variável - a localização da memória - não mudou.

Passar com a refpalavra - chave diz que a variável original e o parâmetro de função apontam para o mesmo local da memória. Isso, novamente, afeta apenas a semântica da atribuição. Se um novo valor for atribuído a uma das variáveis, como os outros apontam para o mesmo local de memória, o novo valor será refletido no outro lado.


11
Observe que o requisito de que o método chamado atribua um valor a um parâmetro out é imposto pelo compilador c # e não pela IL subjacente. Portanto, uma biblioteca escrita em VB.NET pode não estar em conformidade com essa convenção.
jmoreno

Parece que o ref é realmente o equivalente ao símbolo de cancelamento de referência em C ++ (*). A referência de passagem em C # deve ser equivalente ao que o C / C ++ chama de ponteiros duplos (ponteiro para um ponteiro), portanto ref deve desreferenciar o 1º ponteiro, permitindo que o método chamado acesse a localização da memória do objeto real no contexto.
ComeIn

Na verdade, eu sugeriria que um correto TryGetValueusaria refe não outexplicitamente no caso de não encontrar a chave.
NetMage 1/11/2017

27

Depende do contexto de compilação (veja o exemplo abaixo).

oute refambos denotam passagem de variável por referência, mas refexigem que a variável seja inicializada antes de ser passada, o que pode ser uma diferença importante no contexto do Marshaling (Interoperabilidade: UmanagedToManagedTransition ou vice-versa)

O MSDN alerta :

Não confunda o conceito de passagem por referência com o conceito de tipos de referência. Os dois conceitos não são os mesmos. Um parâmetro de método pode ser modificado por ref, independentemente de ser um tipo de valor ou um tipo de referência. Não há boxe de um tipo de valor quando é passado por referência.

Dos documentos oficiais do MSDN:

A palavra-chave out faz com que os argumentos sejam passados ​​por referência. Isso é semelhante à palavra-chave ref, exceto que ref exige que a variável seja inicializada antes de ser passada

A palavra-chave ref faz com que um argumento seja passado por referência, não por valor. O efeito de passar por referência é que qualquer alteração no parâmetro no método é refletida na variável de argumento subjacente no método de chamada. O valor de um parâmetro de referência é sempre o mesmo que o valor da variável de argumento subjacente.

Podemos verificar que a saída e a referência são realmente as mesmas quando o argumento é atribuído:

Exemplo CIL :

Considere o seguinte exemplo

static class outRefTest{
    public static int myfunc(int x){x=0; return x; }
    public static void myfuncOut(out int x){x=0;}
    public static void myfuncRef(ref int x){x=0;}
    public static void myfuncRefEmpty(ref int x){}
    // Define other methods and classes here
}

no CIL, as instruções myfuncOute myfuncRefsão idênticas às esperadas.

outRefTest.myfunc:
IL_0000:  nop         
IL_0001:  ldc.i4.0    
IL_0002:  starg.s     00 
IL_0004:  ldarg.0     
IL_0005:  stloc.0     
IL_0006:  br.s        IL_0008
IL_0008:  ldloc.0     
IL_0009:  ret         

outRefTest.myfuncOut:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRef:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldc.i4.0    
IL_0003:  stind.i4    
IL_0004:  ret         

outRefTest.myfuncRefEmpty:
IL_0000:  nop         
IL_0001:  ret         

nop : nenhuma operação, ldloc : local de carregamento, stloc : local de pilha, ldarg : argumento de carregamento, bs.s : ramificar para o destino ....

(Veja: Lista de instruções CIL )


23

Abaixo estão algumas notas que tirei deste artigo do codeproject em C # Out Vs Ref

  1. Deve ser usado apenas quando esperamos várias saídas de uma função ou método. Um pensamento sobre estruturas também pode ser uma boa opção para o mesmo.
  2. REF e OUT são palavras-chave que determinam como os dados são transmitidos do chamador para o chamado e vice-versa.
  3. Em dados REF passa em dois sentidos. Do chamador ao chamado e vice-versa.
  4. Os dados de entrada saem apenas de uma maneira do chamado para o chamador. Nesse caso, se o chamador tentar enviar dados para o receptor, eles serão ignorados / rejeitados.

Se você é uma pessoa visual, veja este vídeo do seu tubo que demonstra a diferença praticamente https://www.youtube.com/watch?v=lYdcY5zulXA

A imagem abaixo mostra as diferenças mais visualmente

C # Out Vs Ref


11
one-way, os two-waytermos podem ser mal utilizados aqui. Eles realmente são ambos de duas vias, no entanto os seus comportamentos difere conceituais sobre referências e valores dos parâmetros
ibubi

17

Você precisa usar refse planeja ler e gravar no parâmetro Você precisa usar outse planeja escrever. Com efeito, outé para quando você precisa de mais de um valor de retorno ou quando não deseja usar o mecanismo de retorno normal para saída (mas isso deve ser raro).

Existem mecânicos de idiomas que ajudam esses casos de uso. RefOs parâmetros devem ter sido inicializados antes de serem passados ​​para um método (enfatizando o fato de serem de leitura e gravação), e os outparâmetros não podem ser lidos antes de serem atribuídos a um valor e garantidos que foram gravados no final de o método (enfatizando o fato de que eles são somente gravação). A violação desses princípios resulta em um erro em tempo de compilação.

int x;
Foo(ref x); // error: x is uninitialized

void Bar(out int x) {}  // error: x was not written to

Por exemplo, int.TryParseretorna boolae aceita um out intparâmetro:

int value;
if (int.TryParse(numericString, out value))
{
    /* numericString was parsed into value, now do stuff */
}
else
{
    /* numericString couldn't be parsed */
}

Este é um exemplo claro de uma situação em que você precisa gerar dois valores: o resultado numérico e se a conversão foi bem-sucedida ou não. Os autores do CLR decidiram optar por outaqui, pois não se importam com o que intpoderia ter sido antes.

Para ref, você pode olhar para Interlocked.Increment:

int x = 4;
Interlocked.Increment(ref x);

Interlocked.Incrementincrementa atomicamente o valor de x. Como você precisa ler xpara incrementá-lo, essa é uma situação refmais apropriada. Você se preocupa totalmente com o que xera antes de ser passado Increment.

Na próxima versão do C #, será possível declarar variáveis ​​nos outparâmetros, adicionando ainda mais ênfase à natureza somente de saída:

if (int.TryParse(numericString, out int value))
{
    // 'value' exists and was declared in the `if` statement
}
else
{
    // conversion didn't work, 'value' doesn't exist here
}

Obrigado zneak pela sua resposta. Mas você pode me explicar por que não consegui usar para ler e escrever um parâmetro?
Rajbir Singh

@RajbirSingh, porque os outparâmetros não foram necessariamente inicializados, portanto o compilador não permitirá que você leia um outparâmetro até que você tenha escrito algo nele.
Zneak 26/12/13

zneak, eu concordei com você. Mas no exemplo abaixo, um parâmetro out pode ser usado como leitura e gravação: string name = "myName"; private void OutMethod (string de saída nameOut) {if (nameOut == "myName") {nameOut = "Rajbir Singh no método de saída"; }}
Rajbir Singh

11
@RajbirSingh, seu exemplo não é compilado. Você não pode ler nameOutsua ifdeclaração porque ela não foi atribuída a nada antes.
Zneak 26/12/13

Obrigado @zneak. Você está absolutamente certo. Não compila. Muito obrigado pela minha ajuda e agora não faz sentido para mim :)
Rajbir Singh

7

outé uma versão mais restritiva do ref.

Em um corpo de método, você precisa atribuir a todos os outparâmetros antes de sair do método. Além disso, os valores atribuídos a um outparâmetro são ignorados, enquanto refrequer que eles sejam atribuídos.

Então, outvocê pode fazer:

int a, b, c = foo(out a, out b);

onde refexigiria que a e b fossem atribuídos.


Se alguma coisa, outé a versão menos restrita. refpossui "Pré-condição: a variável está definitivamente atribuída, Pós-condição: a variável está definitivamente atribuída", enquanto outpossui apenas `Pós-condição: a variável está definitivamente atribuída". (E como esperado, é necessário mais de uma implementação de função com menos pré-condições)
Ben Voigt

@ BenVoigt: Acho que depende de qual direção você está olhando :) Eu acho que quis dizer restrição em termos de flexibilidade de codificação (?).
leppie

7

Como parece:

a = somente initialize / encher um parâmetro (o parâmetro deve estar vazia) devolvê-lo para fora planície

ref = referência, o parâmetro padrão (talvez com valor), mas a função pode modifiy-lo.


A variável de parâmetro out pode obter um valor antes de passá-lo para um método
Bence Végert

6

Você pode usar a outpalavra-chave contextual em dois contextos (cada um é um link para informações detalhadas), como um modificador de parâmetro ou em declarações de parâmetro de tipo genérico em interfaces e delegados. Este tópico discute o modificador de parâmetro, mas você pode ver este outro tópico para obter informações sobre as declarações de parâmetro de tipo genérico.

A outpalavra-chave faz com que os argumentos sejam passados ​​por referência. É como a refpalavra - chave, exceto que refrequer que a variável seja inicializada antes de ser passada. Para usar um outparâmetro, a definição do método e o método de chamada devem usar explicitamente a outpalavra - chave. Por exemplo: C #

class OutExample
{
    static void Method(out int i)
    {
        i = 44;
    }
    static void Main()
    {
        int value;
        Method(out value);
        // value is now 44
    }
}

Embora as variáveis ​​passadas como outargumentos não precisem ser inicializadas antes de serem passadas, o método chamado é necessário para atribuir um valor antes que o método retorne.

Embora as palavras ref- outchave e causem um comportamento de tempo de execução diferente, elas não são consideradas parte da assinatura do método no momento da compilação. Portanto, os métodos não podem ser sobrecarregados se a única diferença é que um método usa um refargumento e o outro usa um outargumento. O código a seguir, por exemplo, não será compilado: C #

class CS0663_Example
{
    // Compiler error CS0663: "Cannot define overloaded 
    // methods that differ only on ref and out".
    public void SampleMethod(out int i) { }
    public void SampleMethod(ref int i) { }
}

A sobrecarga pode ser feita, no entanto, se um método usa um argumento refou oute o outro não usa, como este: C #

class OutOverloadExample
{
    public void SampleMethod(int i) { }
    public void SampleMethod(out int i) { i = 5; }
}

Propriedades não são variáveis ​​e, portanto, não podem ser passadas como outparâmetros.

Para obter informações sobre como passar matrizes, consulte Passando matrizes usando refe out(Guia de programação em C #).

Você não pode usar as palavras ref- outchave e para os seguintes tipos de métodos:

Async methods, which you define by using the async modifier.

Iterator methods, which include a yield return or yield break statement.

Exemplo

Declarar um outmétodo é útil quando você deseja que um método retorne vários valores. O exemplo a seguir usa outpara retornar três variáveis ​​com uma única chamada de método. Observe que o terceiro argumento é atribuído a nulo. Isso permite que os métodos retornem valores opcionalmente. C #

class OutReturnExample
{
    static void Method(out int i, out string s1, out string s2)
    {
        i = 44;
        s1 = "I've been returned";
        s2 = null;
    }
    static void Main()
    {
        int value;
        string str1, str2;
        Method(out value, out str1, out str2);
        // value is now 44
        // str1 is now "I've been returned"
        // str2 is (still) null;
    }
}

6

Como usar inou outou refem c #?

  • Todas as palavras-chave C#têm a mesma funcionalidade, mas com alguns limites .
  • in argumentos não podem ser modificados pelo método chamado.
  • ref argumentos podem ser modificados.
  • ref deve ser inicializado antes de ser usado pelo chamador, pode ser lido e atualizado no método
  • out argumentos devem ser modificados pelo chamador.
  • out argumentos devem ser inicializados no método
  • Variáveis ​​passadas como inargumentos devem ser inicializadas antes de serem passadas em uma chamada de método. No entanto, o método chamado pode não atribuir um valor ou modificar o argumento.

Você não pode usar as in, refe outpalavras-chave para os seguintes tipos de métodos:

  • Métodos assíncronos , que você define usando o asyncmodificador.
  • Métodos de iterador , que incluem uma declaração yield returnou yield break.

5

Apenas para esclarecer no comentário do OP que o uso em ref e out é uma "referência a um tipo ou estrutura de valor declarado fora do método", que já foi estabelecido incorretamente.

Considere o uso de ref em um StringBuilder, que é um tipo de referência:

private void Nullify(StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// Hi Guy

Em oposição a isso:

private void Nullify(ref StringBuilder sb, string message)
{
    sb.Append(message);
    sb = null;
}

// -- snip --

StringBuilder sb = new StringBuilder();
string message = "Hi Guy";
Nullify(ref sb, message);
System.Console.WriteLine(sb.ToString());

// Output
// NullReferenceException

4

Um argumento passado como ref deve ser inicializado antes de passar para o método, enquanto o parâmetro out não precisa ser inicializado antes de passar para um método.


4

por que você quer usar isso?

Para que outros saibam que a variável será inicializada quando retornar do método chamado!

Como mencionado acima: "para um parâmetro out, o método de chamada é necessário para atribuir um valor antes que o método retorne ."

exemplo:

Car car;
SetUpCar(out car);
car.drive();  // You know car is initialized.

4

Basicamente ambos refe outpara a passagem do objecto / valor entre métodos

A palavra-chave out faz com que os argumentos sejam passados ​​por referência. É como a palavra-chave ref, exceto que ref requer que a variável seja inicializada antes de ser passada.

out : Argumento não é inicializado e deve ser inicializado no método

ref : O argumento já foi inicializado e pode ser lido e atualizado no método

Qual é o uso de "ref" para tipos de referência?

Você pode alterar a referência fornecida para uma instância diferente.

Você sabia?

  1. Embora as palavras-chave ref e out causem comportamento diferente em tempo de execução, elas não são consideradas parte da assinatura do método no tempo de compilação. Portanto, os métodos não podem ser sobrecarregados se a única diferença é que um método usa um argumento ref e o outro usa um argumento out.

  2. Você não pode usar as palavras-chave ref e out para os seguintes tipos de métodos:

    • Métodos assíncronos, que você define usando o modificador assíncrono.
    • Métodos de iterador, que incluem um retorno de rendimento ou uma declaração de quebra de rendimento.
  3. Propriedades não são variáveis ​​e, portanto, não podem ser passadas como parâmetros de saída.


4

Notas extras sobre o C # 7:
No C # 7, não é necessário pré-declarar variáveis ​​usando out. Então, um código como este:

public void PrintCoordinates(Point p)
{
  int x, y; // have to "predeclare"
  p.GetCoordinates(out x, out y);
  WriteLine($"({x}, {y})");
}

Pode ser escrito assim:

public void PrintCoordinates(Point p)
{
  p.GetCoordinates(out int x, out int y);
  WriteLine($"({x}, {y})");
}

Fonte: O que há de novo no C # 7.


4

Ainda sinto a necessidade de um bom resumo, é isso que eu criei.

Resumo,

Quando estamos dentro da função , é assim que especificamos o controle de acesso a dados variáveis ,

in = R

out = deve W antes de R

ref = R + W


Explicação,

in

A função pode apenas ler essa variável.

out

A variável não deve ser inicializada primeiro porque a
função DEVE ESCREVER antes de LER .

ref

A função pode READ / WRITE para essa variável.


Por que é nomeado como tal?

Focando onde os dados são modificados,

in

Os dados devem ser definidos apenas antes da entrada na função (in).

out

Os dados devem ser definidos apenas antes de sair da função (fora).

ref

Os dados devem ser definidos antes de entrar na função (in).
Os dados podem ser definidos antes de sair da função (fora).


talvez (in / out / ref) deva ser renomeado para (r / wr / rw). ou talvez não, in / out é uma metáfora melhor.
Tinker

0

Note-se que iné uma palavra-chave válida a partir do C # ver 7.2 :

O modificador de parâmetro in está disponível no C # 7.2 e posterior. Versões anteriores geram erro do compilador CS8107 ("O recurso 'referências somente leitura' não está disponível no C # 7.0. Use o idioma versão 7.2 ou superior.") Para configurar a versão do idioma do compilador, consulte Selecionar a versão do idioma do C #.

...

A palavra-chave in faz com que os argumentos sejam passados ​​por referência. Torna o parâmetro formal um alias para o argumento, que deve ser uma variável. Em outras palavras, qualquer operação no parâmetro é feita no argumento. É como as palavras-chave ref ou out, exceto que nos argumentos não podem ser modificados pelo método chamado. Enquanto os argumentos ref podem ser modificados, os argumentos out devem ser modificados pelo método chamado, e essas modificações são observáveis ​​no contexto de chamada.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.