¿Cuál es mejor, valor de retorno o parámetro out?


147

Si queremos obtener un valor de un método, podemos usar cualquier valor de retorno, como este:

public int GetValue(); 

o:

public void GetValue(out int x);

Realmente no entiendo las diferencias entre ellos y, por lo tanto, no sé cuál es mejor. ¿Me puedes explicar esto?

Gracias.


3
Desearía que C # tuviera múltiples valores de retorno como Python, por ejemplo.
Trampa

12
@Trap Puede devolver un Tuplesi lo desea, pero el consenso general es que si necesita devolver más de una cosa, las cosas generalmente están relacionadas de alguna manera y esa relación generalmente se expresa mejor como una clase.
Pharap

2
@Pharap Tuples en C # en su forma actual son simplemente feas, pero esa es solo mi opinión. Por otro lado, el "consenso general" no significa nada desde el punto de vista de la usabilidad y la productividad. No crearía una clase para devolver un par de valores por la misma razón por la que no crearía una clase para devolver un par de valores como parámetro de ref / out.
Trampa

@Trap return Tuple.Create(x, y, z);¿No es eso feo? Además, es tarde en el día tenerlos presentados a nivel de idioma. La razón por la que no crearía una clase para devolver valores de un parámetro ref / out es porque los parámetros ref / out solo tienen sentido para grandes estructuras mutables (como matrices) o valores opcionales, y este último es discutible.
Pharap

El equipo de @Pharap C # busca activamente introducir tuplas a nivel de lenguaje. Si bien es bienvenido, la gran cantidad de opciones en .NET son abrumadoras ahora: tipo anónimo, Tuple<>tuplas .NET y C #. Solo deseaba que C # permitiera la devolución de tipos anónimos de métodos con compiladores que infieren tipos (como autoen Dlang).
nawfal

Respuestas:


153

Los valores de retorno son casi siempre la elección correcta cuando el método no tiene nada más que devolver. (De hecho, no puedo pensar en ningún caso en el que alguna vez quisiera un método nulo con un outparámetro, si tuviera la opción. Los Deconstructmétodos de C # 7 para la deconstrucción basada en el lenguaje actúan como una excepción muy, muy rara a esta regla .)

Aparte de cualquier otra cosa, evita que la persona que llama tenga que declarar la variable por separado:

int foo;
GetValue(out foo);

vs

int foo = GetValue();

Los valores de salida también evitan el encadenamiento de métodos de esta manera:

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

(De hecho, ese es uno de los problemas con los establecedores de propiedades también, y es por eso que el patrón del generador utiliza métodos que devuelven el generador, por ejemplo myStringBuilder.Append(xxx).Append(yyy) ).

Además, los parámetros externos son un poco más difíciles de usar con la reflexión y, por lo general, también dificultan las pruebas. (Por lo general, se hace un mayor esfuerzo para facilitar la burla de los valores de retorno que los parámetros). Básicamente no hay nada en lo que pueda pensar que hagan más fácil ...

Valores de retorno FTW.

EDITAR: en términos de lo que está pasando ...

Básicamente, cuando se pasa en un argumento para un parámetro "out", que tiene que pasar en una variable. (Los elementos de la matriz también se clasifican como variables). El método que llama no tiene una variable "nueva" en su pila para el parámetro: utiliza su variable para el almacenamiento. Cualquier cambio en la variable es inmediatamente visible. Aquí hay un ejemplo que muestra la diferencia:

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

La diferencia está en el paso "post", es decir, después de que la variable o parámetro local ha cambiado. En la prueba ReturnValue, esto no hace ninguna diferencia con la valuevariable estática . En la prueba OutParameter, la valuevariable cambia la líneatmp = 10;


2
Me hiciste creer que el valor de retorno es mucho mejor :). Pero todavía me pregunto qué sucede "en profundidad". Quiero decir, el valor de retorno y el parámetro out, ¿son diferentes en la forma en que se crean, asignan y devuelven?
Quan Mai

1
TryParse es el mejor ejemplo de cuando usar un parámetro externo es apropiado y limpio. Aunque lo he usado en casos especiales como if (WorkSucceeded (out List <string> errors) que es básicamente el mismo patrón que TryParse
Chad Grant

2
Una respuesta pobre e inútil. Como se explica en los comentarios sobre esta pregunta , nuestros parámetros tienen varias ventajas útiles. La preferencia entre out vs return debe depender de la situación.
aaronsnoswell

2
@aaronsnoswell: ¿Qué comentarios precisos? Tenga en cuenta que el ejemplo de noDictionary.TryGetValue es aplicable aquí, ya que no es un método nulo. ¿Puedes explicar por qué quieres un parámetro en lugar de un valor de retorno? (Incluso , preferiría un valor de retorno que contenga toda la información de salida, personalmente. Vea NodaTime's para ver un ejemplo de cómo lo habría diseñado.)outTryGetValueParseResult<T>
Jon Skeet

2
@ Jim: Creo que tendremos que estar de acuerdo en no estar de acuerdo. Mientras veo su punto de martilleo sobre la gente en la cabeza con esto, sino que también hace que sea menos amigable para los que no saben lo que están haciendo. Lo bueno de una excepción en lugar de un valor de retorno es que no puede ignorarlo fácilmente y continuar como si nada sucediera ... mientras que con un valor de retorno y un outparámetro, simplemente no puede hacer nada con el valor.
Jon Skeet

26

Lo que es mejor, depende de su situación particular. Una de las razones que outexisten es para facilitar la devolución de múltiples valores de una llamada a método:

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

    return input;
}

Entonces, uno no es por definición mejor que el otro. Pero, por lo general, querrá usar un retorno simple, a menos que tenga la situación anterior, por ejemplo.

EDITAR: Esta es una muestra que demuestra una de las razones por las que existe la palabra clave. Lo anterior no se debe considerar como una mejor práctica.


2
No estoy de acuerdo con esto, ¿por qué no devolverías una estructura de datos con 4 ints? Esto es realmente confuso.
Chad Grant

1
Obviamente, hay más (y mejores) formas de devolver múltiples valores, solo estoy dando al OP una razón por la cual existe en primer lugar.
pyrocumulus

2
Estoy de acuerdo con @Cloud. El hecho de que no sea la mejor manera no significa que no deba existir.
Cerebrus

Bueno, el código ni siquiera se compilará para uno ... y al menos debería mencionar que se considera una mala práctica / no se prefiere.
Chad Grant

1
¿No se compilará? ¿Y por qué es eso? Esta muestra podría compilar simplemente perfecto. Y por favor, estoy dando una muestra de por qué existe una sintaxis / palabra clave en particular, no una lección sobre las mejores prácticas.
pyrocumulus

23

En general, debería preferir un valor de retorno sobre un parámetro de salida. Los params son un mal necesario si te encuentras escribiendo un código que necesita hacer 2 cosas. Un buen ejemplo de esto es el patrón Try (como Int32. TryParse).

Consideremos lo que la persona que llama de sus dos métodos tendría que hacer. Para el primer ejemplo, puedo escribir esto ...

int foo = GetValue();

Tenga en cuenta que puedo declarar una variable y asignarla a través de su método en una línea. Para el segundo ejemplo se ve así ...

int foo;
GetValue(out foo);

Ahora me veo obligado a declarar mi variable por adelantado y escribir mi código en dos líneas.

actualizar

Un buen lugar para mirar al hacer este tipo de preguntas son las Pautas de diseño de .NET Framework. Si tiene la versión del libro, puede ver las anotaciones de Anders Hejlsberg y otros sobre este tema (página 184-185), pero la versión en línea está aquí ...

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

Si necesita devolver dos cosas de una API, entonces envolverlas en una estructura / clase sería mejor que un parámetro externo.


Gran respuesta, especialmente la referencia a TryParse, una función (común) que obliga a los desarrolladores a utilizar las variables (poco comunes).
Cerebrus

12

Hay una razón para usar un outparámetro que aún no se ha mencionado: el método de llamada está obligado a recibirlo. Si su método produce un valor que la persona que llama no debe descartar, lo que outobliga a la persona que llama a aceptarlo específicamente:

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

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

Por supuesto, la persona que llama aún puede ignorar el valor en un outparámetro, pero usted ha llamado su atención.

Esta es una necesidad rara; con mayor frecuencia, debe usar una excepción para un problema genuino o devolver un objeto con información de estado para un "FYI", pero podría haber circunstancias en las que esto sea importante.


8

Es preferencia principalmente

Prefiero devoluciones y si tiene devoluciones múltiples, puede envolverlas en un resultado DTO

public class Result{
  public Person Person {get;set;}
  public int Sum {get;set;}
}

5

Solo puede tener un valor de retorno, mientras que puede tener múltiples parámetros de salida.

Solo necesita considerar los parámetros en esos casos.

Sin embargo, si necesita devolver más de un parámetro de su método, probablemente desee ver lo que está devolviendo de un enfoque OO y considerar si es mejor devolver un objeto o una estructura con estos parámetros. Por lo tanto, vuelve a un valor de retorno nuevamente.


5

Casi siempre debe usar un valor de retorno. ' out' los parámetros crean un poco de fricción para muchas API, composición, etc.

La excepción más notable que viene a la mente es cuando desea devolver múltiples valores (.Net Framework no tiene tuplas hasta 4.0), como con el TryParsepatrón.


No estoy seguro de si es una buena práctica, pero también se puede usar Arraylist para devolver múltiples valores.
BA

@BA no, no es una buena práctica, otros usuarios no sabrían qué contiene esta Arraylist o en qué posición se encuentran. Por ejemplo, quiero devolver la cantidad de bytes leídos y también el valor. fuera o tuplas que se nombran sería mucho mejor. ArrayList, o cualquier otra lista, solo sería útil si desea devolver una colección de cosas, por ejemplo, una lista de personas.
sLw

2

Preferiría lo siguiente en lugar de cualquiera de los de este simple ejemplo.

public int Value
{
    get;
    private set;
}

Pero, todos son muy parecidos. Por lo general, uno solo usaría 'out' si necesitan pasar múltiples valores del método. Si desea enviar un valor dentro y fuera del método, uno elegiría 'ref'. Mi método es el mejor, si solo está devolviendo un valor, pero si desea pasar un parámetro y recuperar un valor, es probable que elija su primera opción.


2

Creo que uno de los pocos escenarios en los que sería útil sería trabajar con memoria no administrada, y desea que sea obvio que el valor "devuelto" debe eliminarse manualmente, en lugar de esperar que se elimine por sí solo. .


2

Además, los valores de retorno son compatibles con paradigmas de diseño asíncrono.

No puede designar una función "asíncrona" si usa parámetros ref o out.

En resumen, los valores de retorno permiten el encadenamiento de métodos, una sintaxis más limpia (al eliminar la necesidad de que la persona que llama declare variables adicionales) y permiten diseños asincrónicos sin la necesidad de modificaciones sustanciales en el futuro.


Gran punto en la designación "asíncrona". Pensé que alguien más lo habría mencionado. El encadenamiento, así como el uso del valor de retorno (de hecho, la función en sí misma) como expresión es otro beneficio clave. Realmente es la diferencia entre considerar solo cómo recuperar las cosas de un proceso (el "cubo" - discusión Tuple / class / struct) y tratar el proceso en sí como una expresión que puede ser sustituida por un solo valor (la utilidad del funciona en sí, porque solo devuelve 1 valor).
usuario1172173

1

Ambos tienen un propósito diferente y el compilador no los trata de la misma manera. Si su método necesita devolver un valor, debe usar return. Out se usa cuando su método necesita devolver múltiples valores.

Si usa return, los datos se escriben primero en la pila de métodos y luego en los métodos de llamada. Si bien en caso de salida, se escribe directamente en la pila de métodos de llamada. No estoy seguro si hay más diferencias.


¿Los métodos se apilan? No soy un experto en C #, pero x86 solo admite una pila por subproceso. El "marco" del método se desasigna durante el retorno, y si sucede un cambio de contexto, la pila desasignada puede sobrescribirse. En c todos los valores de retorno van en el registro eax. Si desea devolver objetos / estructuras, deben asignarse al montón y el puntero se colocará en eax.
Stefan Lundström

1

Como han dicho otros: valor de retorno, no fuera param.

¿Puedo recomendarle el libro "Framework Design Guidelines" (2nd ed)? Las páginas 184-185 cubren las razones para evitar los params. Todo el libro lo guiará en la dirección correcta en todo tipo de problemas de codificación .NET.

Aliado con las Pautas de diseño del marco está el uso de la herramienta de análisis estático, FxCop. Encontrará esto en los sitios de Microsoft como una descarga gratuita. Ejecute esto en su código compilado y vea lo que dice. Si se queja de cientos y cientos de cosas ... ¡no se asuste! Mire con calma y cuidado lo que dice sobre cada caso. No te apresures a arreglar las cosas lo antes posible. Aprende de lo que te está diciendo. Serás puesto en el camino hacia el dominio.


1

El uso de la palabra clave out con un tipo de retorno de bool, a veces puede reducir la hinchazón de código y aumentar la legibilidad. (Principalmente cuando a menudo se ignora la información adicional en el parámetro de salida). Por ejemplo:

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;
        }
    }
}

1

No hay diferencia real. Los parámetros de salida están en C # para permitir que el método devuelva más de un valor, eso es todo.

Sin embargo, existen algunas pequeñas diferencias, pero ninguna de ellas es realmente importante:

Usar el parámetro lo obligará a usar dos líneas como:

int n;
GetValue(n);

mientras usa el valor de retorno le permitirá hacerlo en una línea:

int n = GetValue();

Otra diferencia (corregir solo para los tipos de valor y solo si C # no incluye la función en línea) es que el uso del valor de retorno necesariamente hará una copia del valor cuando la función regrese, mientras que el uso del parámetro OUT no necesariamente lo hará.


0

out es más útil cuando intenta devolver un objeto que declara en el método.

Ejemplo

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;
}

0

El valor de retorno es el valor normal que devuelve su método.

Donde como parámetro out , well out y ref son 2 palabras clave de C #, permiten pasar variables como referencia .

La gran diferencia entre ref y out es que ref debe inicializarse antes y out no


-2

Sospecho que no voy a echar un vistazo a esta pregunta, pero soy muy programador experimentado y espero que algunos de los lectores más abiertos presten atención.

Creo que se adapta mejor a los lenguajes de programación orientados a objetos para que sus procedimientos de retorno de valor (VRP) sean deterministas y puros.

'VRP' es el nombre académico moderno para una función que se llama como parte de una expresión y tiene un valor de retorno que reemplaza teóricamente la llamada durante la evaluación de la expresión. Por ejemplo, en una declaración como x = 1 + f(y)la función festá sirviendo como un VRP.

'Determinista' significa que el resultado de la función depende solo de los valores de sus parámetros. Si lo vuelve a llamar con los mismos valores de parámetro, seguramente obtendrá el mismo resultado.

'Puro' significa que no hay efectos secundarios: llamar a la función no hace nada excepto calcular el resultado. Esto puede interpretarse en el sentido de no importante efectos secundarios , en la práctica, por lo que si el VRP emite un mensaje de depuración cada vez que se llama, por ejemplo, eso probablemente se puede ignorar.

Por lo tanto, si, en C #, su función no es determinista y pura, le digo que debe hacerla una voidfunción (en otras palabras, no un VRP), y cualquier valor que necesite devolver debe devolverse en uno outo en un refparámetro.

Por ejemplo, si tiene una función para eliminar algunas filas de una tabla de base de datos y desea que devuelva el número de filas que eliminó, debe declararlo de la siguiente manera:

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

Si a veces desea llamar a esta función pero no obtener la count, siempre puede declarar una sobrecarga.

Es posible que desee saber por qué este estilo se adapta mejor a la programación orientada a objetos. En términos generales, se ajusta a un estilo de programación que podría llamarse (un poco impreciso) 'programación de procedimientos', y es un estilo de programación de procedimientos que se adapta mejor a la programación orientada a objetos.

¿Por qué? El modelo clásico de los objetos es que tienen propiedades (también conocidos como atributos), y usted interroga y manipula el objeto (principalmente) mediante la lectura y actualización de esas propiedades. Un estilo de programación de procedimientos tiende a facilitarlo, ya que puede ejecutar código arbitrario entre operaciones que obtienen y establecen propiedades.

La desventaja de la programación de procedimientos es que, debido a que puede ejecutar código arbitrario por todas partes, puede obtener algunas interacciones muy obtusas y vulnerables a los errores a través de variables globales y efectos secundarios.

Entonces, simplemente, es una buena práctica señalar a alguien que lee su código que una función podría tener efectos secundarios al hacer que no devuelva ningún valor.


> Si a veces desea llamar a esta función pero no obtener el recuento, siempre puede declarar una sobrecarga. En C # versión 7 (creo) y posterior, puede usar el _símbolo de descarte para ignorar un parámetro de salida, por ejemplo: DeleteBasketItems (category, out _);
Debatador
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.