Qual é o melhor valor de retorno ou parâmetro out?

147

Se queremos obter um valor de um método, podemos usar um valor de retorno, como este:

public int GetValue(); 

ou:

public void GetValue(out int x);

Eu realmente não entendo as diferenças entre eles e, portanto, não sei o que é melhor. Você pode me explicar isso?

Obrigado.

Quan Mai
fonte
3
Eu gostaria que o C # tivesse vários valores de retorno como Python, por exemplo.
Trap
12
@Trap Você pode retornar um Tuplese quiser, mas o consenso geral é que, se você precisar retornar mais de uma coisa, as coisas geralmente estão relacionadas de alguma forma e essa relação geralmente é melhor expressa como uma classe.
Pharap
2
Tuplas do @rapha em C # na sua forma atual são apenas feias, mas essa é apenas a minha opinião. Por outro lado, "consenso geral" não significa nada do ponto de vista da usabilidade e da produtividade. Você não criaria uma classe para retornar alguns valores pelo mesmo motivo que não criaria uma classe para retornar alguns valores como um parâmetro ref / out.
Trap
@ Trap return Tuple.Create(x, y, z);não é tão feio. Além disso, é tarde para que eles sejam introduzidos no nível do idioma. O motivo de eu não criar uma classe para retornar valores de um parâmetro ref / out é porque os parâmetros ref / out realmente fazem sentido para grandes estruturas mutáveis ​​(como matrizes) ou valores opcionais, e o último é discutível.
Pharap
A equipe do @Pharap C # está procurando ativamente introduzir tuplas no nível do idioma. Embora seja bem-vindo toda a infinidade de opções no .NET, agora são esmagadoras - tipo anônimo, .NET Tuple<>e C # tuplas !. Eu apenas desejei que o C # permitisse o retorno de tipos anônimos a partir de métodos com tipo de autodedução do compilador (como em Dlang).
Nawfal

Respostas:

153

Os valores de retorno quase sempre são a escolha certa quando o método não tem mais nada para retornar. (Na verdade, eu não consigo pensar em quaisquer casos onde eu nunca querem um método vazio com um outparâmetro, se eu tivesse a escolha. C # 7 de Deconstructmétodos para a desconstrução apoiada em língua atua como um muito, muito rara exceção a esta regra .)

Além de qualquer outra coisa, impede o chamador de declarar a variável separadamente:

int foo;
GetValue(out foo);

vs

int foo = GetValue();

Os valores de saída também impedem o encadeamento de métodos como este:

Console.WriteLine(GetValue().ToString("g"));

(De fato, esse também é um dos problemas com os configuradores de propriedades, e é por isso que o padrão do construtor usa métodos que retornam o construtor, por exemplo myStringBuilder.Append(xxx).Append(yyy).)

Além disso, os parâmetros de saída são um pouco mais difíceis de usar com reflexão e geralmente tornam os testes mais difíceis também. (Geralmente, mais esforço é feito para facilitar a simulação de valores de retorno do que parâmetros fora). Basicamente, não há nada que eu possa pensar que eles facilitem ...

Retorna valores FTW.

EDIT: Em termos do que está acontecendo ...

Basicamente, quando você passa um argumento para um parâmetro "out", precisa passar uma variável. (Os elementos de matriz também são classificados como variáveis.) O método que você chama não possui uma variável "nova" em sua pilha para o parâmetro - ele usa sua variável para armazenamento. Quaisquer alterações na variável são imediatamente visíveis. Aqui está um exemplo mostrando a diferença:

using System;

class Test
{
    static int value;

    static void ShowValue(string description)
    {
        Console.WriteLine(description + value);
    }

    static void Main()
    {
        Console.WriteLine("Return value test...");
        value = 5;
        value = ReturnValue();
        ShowValue("Value after ReturnValue(): ");

        value = 5;
        Console.WriteLine("Out parameter test...");
        OutParameter(out value);
        ShowValue("Value after OutParameter(): ");
    }

    static int ReturnValue()
    {
        ShowValue("ReturnValue (pre): ");
        int tmp = 10;
        ShowValue("ReturnValue (post): ");
        return tmp;
    }

    static void OutParameter(out int tmp)
    {
        ShowValue("OutParameter (pre): ");
        tmp = 10;
        ShowValue("OutParameter (post): ");
    }
}

Resultados:

Return value test...
ReturnValue (pre): 5
ReturnValue (post): 5
Value after ReturnValue(): 10
Out parameter test...
OutParameter (pre): 5
OutParameter (post): 10
Value after OutParameter(): 10

A diferença está na etapa "pós" - ou seja, após a variável ou parâmetro local ter sido alterado. No teste ReturnValue, isso não faz diferença para a valuevariável estática . No teste OutParameter, a valuevariável é alterada pela linhatmp = 10;

Jon Skeet
fonte
2
Você me fez acreditar que o valor de retorno é muito melhor :). Mas ainda me pergunto o que acontece "em profundidade". Quero dizer, o valor de retorno e o parâmetro out são diferentes na maneira como são criados, atribuídos e retornados?
Quan Mai
1
O TryParse é o melhor exemplo de quando usar um parâmetro externo é apropriado e limpo. Apesar de eu tê-lo usado em casos especiais, como if (WorkSucceeded (a List <string> erros) que é basicamente o mesmo padrão de TryParse
Chad Grant
2
Uma resposta pobre e inútil. Conforme explicado nos comentários sobre esta questão , os parâmetros out têm várias vantagens úteis. A preferência entre out vs return deve depender da situação.
aaronsnoswell
2
@aaronsnoswell: Quais comentários precisos? Lembre-se de que o exemplo de nãoDictionary.TryGetValue é aplicável aqui, pois não é um método nulo. Você pode explicar por que deseja um parâmetro em vez de um valor de retorno? (Mesmo para , eu prefiro um valor de retorno que contém todas as informações de saída, pessoalmente Veja NodaTime de. Para um exemplo de como eu teria projetou.)outTryGetValueParseResult<T>
Jon Skeet
2
@ Jim: Acho que teremos que concordar em discordar. Enquanto eu vejo o seu ponto sobre martelando as pessoas sobre a cabeça com isso, ele também torna menos amigável para aqueles que não sabem o que estão fazendo. O bom de uma exceção em vez de um valor de retorno é que você não pode ignorá- la facilmente e continuar como se nada tivesse acontecido ... enquanto que com um valor de retorno e um outparâmetro, você simplesmente não pode fazer nada com o valor.
21814 Jon Skeet
26

O que é melhor, depende da sua situação particular. Um dos motivos outexiste é facilitar o retorno de vários valores de uma chamada de método:

public int ReturnMultiple(int input, out int output1, out int output2)
{
    output1 = input + 1;
    output2 = input + 2;

    return input;
}

Portanto, um não é, por definição, melhor que o outro. Mas geralmente você deseja usar um retorno simples, a menos que tenha a situação acima, por exemplo.

EDIT: este é um exemplo que demonstra uma das razões pelas quais a palavra-chave existe. O exposto acima não deve ser considerado uma prática recomendada.

pyrocumulus
fonte
2
Não concordo com isso, por que você não retornaria uma estrutura de dados com 4 ints? Isso é realmente confuso.
Chad Grant
1
Obviamente, existem mais (e melhores) maneiras de retornar vários valores, apenas estou dando ao OP uma razão pela qual existe em primeiro lugar.
Pyrocumulus
1
Eu concordo com o @Cloud. Só porque não é o melhor caminho, não significa que não deveria existir.
Cerebrus
Bem, o código nem será compilado para um ... e deve pelo menos mencionar que é considerado uma má prática / não preferido.
Chad Grant
1
Não vai compilar? E por que isto? Esta amostra pode compilar perfeitamente. E, por favor, estou fornecendo uma amostra do motivo pelo qual existe uma sintaxe / palavra-chave específica, não uma lição sobre práticas recomendadas.
Pyrocumulus
23

Geralmente, você deve preferir um valor de retorno a um parâmetro de saída. Parâmetros externos são um mal necessário se você se encontra escrevendo um código que precisa fazer duas coisas. Um bom exemplo disso é o padrão Try (como Int32.TryParse).

Vamos considerar o que o chamador dos seus dois métodos teria que fazer. Para o primeiro exemplo, eu posso escrever isso ...

int foo = GetValue();

Observe que eu posso declarar uma variável e atribuí-la através do seu método em uma linha. No segundo exemplo, fica assim ...

int foo;
GetValue(out foo);

Agora sou forçado a declarar minha variável antecipadamente e escrever meu código em duas linhas.

atualizar

Um bom lugar para procurar ao fazer esses tipos de pergunta é o .NET Framework Design Guidelines. Se você possui a versão em livro, pode ver as anotações de Anders Hejlsberg e outras pessoas sobre esse assunto (página 184-185), mas a versão on-line está aqui ...

http://msdn.microsoft.com/en-us/library/ms182131(VS.80).aspx

Se você precisar retornar duas coisas de uma API, agrupá-las em uma struct / classe seria melhor do que um parâmetro externo.

Martin Peck
fonte
Ótima resposta, especialmente a referência ao TryParse, uma função (comum) que força os desenvolvedores a usar as variáveis ​​de saída (incomum).
Cerebrus
12

Há uma razão para usar um outparâmetro que ainda não foi mencionado: o método de chamada é obrigado a recebê-lo. Se o seu método produz um valor que o chamador não deve descartar, outobriga o chamador a aceitá-lo especificamente:

 Method1();  // Return values can be discard quite easily, even accidentally

 int  resultCode;
 Method2(out resultCode);  // Out params are a little harder to ignore

É claro que o chamador ainda pode ignorar o valor em um outparâmetro, mas você chamou a atenção dele.

Essa é uma necessidade rara; com mais freqüência, você deve usar uma exceção para um problema genuíno ou retornar um objeto com informações de estado para um "FYI", mas pode haver circunstâncias em que isso é importante.


fonte
8

É preferência principalmente

Prefiro devoluções e se você tiver várias devoluções, poderá envolvê-las em um resultado DTO

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}
Scott Cowan
fonte
5

Você pode ter apenas um valor de retorno enquanto pode ter vários parâmetros de saída.

Você só precisa considerar parâmetros nesses casos.

No entanto, se você precisar retornar mais de um parâmetro do seu método, provavelmente desejará examinar o que está retornando de uma abordagem OO e considerar se é melhor retornar um objeto ou uma estrutura com esses parâmetros. Portanto, você voltou a um valor de retorno novamente.

Robin Day
fonte
5

Você quase sempre deve usar um valor de retorno. ' out' parâmetros criam um pouco de atrito para muitas APIs, composicionalidade etc.

A exceção mais notável que vem à mente é quando você deseja retornar vários valores (o .Net Framework não possui tuplas até 4.0), como no TryParsepadrão.

Brian
fonte
Não tenho certeza se é uma boa prática, mas o Arraylist também pode ser usado para retornar vários valores.
BA
@BA não, não é uma boa prática, outros usuários não saberiam o que esta lista de matrizes contém ou em que posição estão. Por exemplo, quero retornar a quantidade de bytes lidos e também o valor. out ou tuplas nomeadas seriam muito melhores. ArrayList ou qualquer outra lista seria útil apenas se você quiser retornar uma coleção de coisas, por exemplo, uma lista de pessoas.
SLw
2

Eu preferiria o seguinte em vez de qualquer um desses exemplos simples.

public int Value
{
    get;
    private set;
}

Mas, eles são todos iguais. Normalmente, só seria usado 'out' se eles precisassem passar vários valores de volta do método. Se você deseja enviar um valor dentro e fora do método, escolha-se 'ref'. Meu método é o melhor, se você está retornando apenas um valor, mas se deseja passar um parâmetro e recuperar um valor, provavelmente você escolheria sua primeira opção.

kenny
fonte
2

Eu acho que um dos poucos cenários em que seria útil seria ao trabalhar com memória não gerenciada, e você deseja deixar óbvio que o valor "retornado" deve ser descartado manualmente, em vez de esperar que seja descartado por conta própria .

xian
fonte
2

Além disso, os valores de retorno são compatíveis com os paradigmas de design assíncrono.

Você não pode designar uma função "assíncrona" se ela usar parâmetros ref ou out.

Em resumo, os valores de retorno permitem o encadeamento de métodos, uma sintaxe mais limpa (eliminando a necessidade de o chamador declarar variáveis ​​adicionais) e permitem designs assíncronos sem a necessidade de modificações substanciais no futuro.

firetiger77
fonte
Ótimo ponto sobre a designação "assíncrona". Pensei que alguém teria mencionado isso. Encadeamento - bem como usar o valor de retorno (na verdade a própria função) como uma expressão é outro benefício importante. É realmente a diferença entre considerar apenas como recuperar as coisas de um processo (o "bloco" - discussão em tupla / classe / estrutura) e tratar o próprio processo como uma expressão que pode ser substituída por um único valor (a utilidade do método função, porque retorna apenas 1 valor).
user1172173
1

Ambos têm um propósito diferente e não são tratados da mesma forma pelo compilador. Se seu método precisar retornar um valor, você deverá usar return. Out é usado onde seu método precisa retornar vários valores.

Se você usar return, os dados serão gravados primeiro na pilha de métodos e depois nos métodos de chamada. Enquanto no caso de fora, ele é gravado diretamente na pilha de métodos de chamada. Não tenho certeza se existem mais diferenças.

dinamarquês
fonte
Os métodos empilham? Eu não sou especialista em c #, mas o x86 suporta apenas uma pilha por thread. O "quadro" do método é desalocado durante o retorno e, se uma troca de contexto ocorrer, a pilha desalocada pode ser substituída. Em c, todos os valores de retorno entram no registro eax. Se você deseja retornar objetos / estruturas, eles precisam ser alocados ao heap e o ponteiro será colocado em eax.
Stefan Lundström
1

Como outros já disseram: retorne valor, não fora param.

Posso recomendar o livro "Framework Design Guidelines" (2ª ed)? As páginas 184-185 abordam as razões para evitar os params. O livro inteiro orientará você na direção certa em todos os tipos de problemas de codificação do .NET.

Aliado ao Framework Design Guidelines é o uso da ferramenta de análise estática, FxCop. Você encontrará isso nos sites da Microsoft como um download gratuito. Execute isso no seu código compilado e veja o que diz. Se reclamar de centenas e centenas de coisas ... não entre em pânico! Olhe com calma e cuidado para o que diz sobre cada caso. Não se apresse para consertar as coisas o mais rápido possível. Aprenda com o que está dizendo. Você será colocado no caminho do domínio.

Andrew Webb
fonte
1

Usar a palavra-chave out com um tipo de retorno bool, às vezes pode reduzir o inchaço do código e aumentar a legibilidade. (Principalmente quando as informações extras nos parâmetros de saída são frequentemente ignoradas.) Por exemplo:

var result = DoThing();
if (result.Success)
{
    result = DoOtherThing()
    if (result.Success)
    {
        result = DoFinalThing()
        if (result.Success)
        {
            success = true;
        }
    }
}

vs:

var result;
if (DoThing(out result))
{
    if (DoOtherThing(out result))
    {
        if (DoFinalThing(out result))
        {
            success = true;
        }
    }
}
Paul Smith
fonte
1

Não há diferença real. Os parâmetros de saída estão em C # para permitir que o método retorne mais de um valor, só isso.

No entanto, existem algumas pequenas diferenças, mas nenhuma delas é realmente importante:

Usar o parâmetro forçará você a usar duas linhas como:

int n;
GetValue(n);

ao usar o valor de retorno, você poderá fazer isso em uma linha:

int n = GetValue();

Outra diferença (correta apenas para os tipos de valor e apenas se o C # não incorporar a função) é que o uso do valor de retorno fará necessariamente uma cópia do valor quando a função retornar, enquanto o uso do parâmetro OUT não o fará necessariamente.

user88637
fonte
0

out é mais útil quando você está tentando retornar um objeto que você declara no método

Exemplo

public BookList Find(string key)
{
   BookList book; //BookList is a model class
   _books.TryGetValue(key, out book) //_books is a concurrent dictionary
                                     //TryGetValue gets an item with matching key and returns it into book.
   return book;
}
Taera Kwon
fonte
0

valor de retorno é o valor normal retornado pelo seu método.

Onde como parâmetro out , well out e ref são 2 palavras-chave em C #, eles permitem passar variáveis ​​como referência .

A grande diferença entre ref e out é que ref deve ser inicializado antes e fora , não

bizimunda
fonte
-2

Eu suspeito que não vou dar uma olhada nessa questão, mas sou muito programador experiente e espero que alguns dos leitores mais abertos prestem atenção.

Acredito que ele se adapte melhor às linguagens de programação orientadas a objetos para que seus procedimentos de retorno de valor (VRPs) sejam determinísticos e puros.

'VRP' é o nome acadêmico moderno para uma função que é chamada como parte de uma expressão e tem um valor de retorno que substitui nocionalmente a chamada durante a avaliação da expressão. Por exemplo, em uma declaração como x = 1 + f(y)a funçãof está servindo como um VRP.

'Determinístico' significa que o resultado da função depende apenas dos valores de seus parâmetros. Se você chamá-lo novamente com os mesmos valores de parâmetro, certamente obterá o mesmo resultado.

'Puro' significa sem efeitos colaterais: chamar a função não faz nada, exceto calcular o resultado. Isso pode ser interpretado como significando que não é importante efeitos colaterais , na prática; portanto, se o VRP emitir uma mensagem de depuração toda vez que for chamado, por exemplo, isso provavelmente poderá ser ignorado.

Portanto, se, em C #, sua função não é determinística e pura, eu digo que você deve torná-la uma void função (em outras palavras, não um VRP), e qualquer valor que ele precise retornar deve ser retornado em um outou em um refparâmetro.

Por exemplo, se você tem uma função para excluir algumas linhas de uma tabela de banco de dados e deseja que ela retorne o número de linhas excluídas, deve declarar algo assim:

public void DeleteBasketItems(BasketItemCategory category, out int count);

Se, às vezes, você quiser chamar essa função, mas não obtê-la count, sempre poderá declarar uma sobrecarga.

Você pode querer saber por que esse estilo combina melhor com a programação orientada a objetos. Em termos gerais, ele se encaixa em um estilo de programação que pode ser (um pouco impreciso) denominado 'programação procedural', e é um estilo de programação procedural que se encaixa melhor na programação orientada a objetos.

Por quê? O modelo clássico de objetos é que eles têm propriedades (também conhecidas como atributos), e você interroga e manipula o objeto (principalmente) lendo e atualizando essas propriedades. Um estilo de programação procedural tende a facilitar isso, porque você pode executar código arbitrário entre operações que obtêm e configuram propriedades.

A desvantagem da programação processual é que, como você pode executar código arbitrário em todo o lugar, pode obter algumas interações muito obtusas e vulneráveis ​​a erros por meio de variáveis ​​globais e efeitos colaterais.

Portanto, de maneira simples, é uma boa prática sinalizar para alguém que está lendo seu código que uma função pode ter efeitos colaterais, tornando-a sem retorno de valor.

debatedor
fonte
> Se, às vezes, você quiser chamar essa função, mas não conseguir a contagem, sempre poderá declarar uma sobrecarga. No C # versão 7 (acho) e posterior, você pode usar o _símbolo de descarte para ignorar um parâmetro out, por exemplo: DeleteBasketItems (category, out _);
debated