practices - remarks c#
¿Cuándo debería usar los parámetros? (10)
A veces uso parámetros para la legibilidad, cuando leer el nombre del método es más importante que cualquiera que sea el resultado del método, particularmente para métodos que ejecutan comandos además de devolver resultados.
StatusInfo a, b, c;
Initialize(out a);
Validate(a, out b);
Process(b, out c);
vs.
StatusInfo a = Initialize();
StatusInfo b = Validate(a);
StatusInfo c = Process(b);
Al menos para mí, pongo mucho énfasis en los primeros caracteres de cada línea cuando estoy escaneando. Puedo ver fácilmente qué está sucediendo en el primer ejemplo después de reconocer que algunas variables de "StatusInfo" están declaradas. En el segundo ejemplo, lo primero que veo es que se recupera un montón de StatusInfo. Tengo que escanear por segunda vez para ver qué tipo de efectos pueden tener los métodos.
No entiendo cuándo se debe usar un parámetro de salida, personalmente envuelvo el resultado en un nuevo tipo si necesito devolver más de un tipo, me resulta mucho más fácil trabajar que hacerlo.
He visto un método como este,
public void Do(int arg1, int arg2, out int result)
¿Hay algún caso en que eso realmente tenga sentido?
¿ TryParse
, por qué no devolver un tipo ParseResult
? o en el marco más nuevo, ¿devuelve un tipo anulable?
Años tarde con una respuesta, lo sé. out (y ref también) también es realmente útil si no desea que su método haga una instancia de un nuevo objeto para devolver. Esto es muy relevante en los sistemas de alto rendimiento en los que desea lograr un rendimiento de microsegundo inferior para su método. instanciar es relativamente costoso visto desde una perspectiva de acceso a la memoria.
Bueno, como con la mayoría de las cosas, depende. Echemos un vistazo a las opciones
- puedes devolver lo que quieras como el valor de retorno de la función
- si desea devolver valores múltiples o si la función ya tiene un valor de retorno, puede usar parámetros o crear un nuevo tipo compuesto que exponga todos estos valores como propiedades
En el caso de TryParse, usar un parámetro out es eficiente: no tiene que crear un nuevo tipo que sea 16B de sobrecarga (en máquinas 32b) o incurrir en el costo de perforación de tenerlos recolectados después de la llamada. Por ejemplo, se podría llamar a TryParse desde dentro de un bucle, de modo que aquí se usa la regla de params.
Para funciones que no se llamarían dentro de un bucle (es decir, el rendimiento no es una preocupación importante), devolver un solo objeto compuesto podría ser ''más limpio'' (subjetivo para el espectador). Ahora con tipos anónimos y tipado dinámico, puede ser aún más fácil.
Nota:
-
out
parámetros tienen algunas reglas que deben seguirse, es decir, el compilador se asegurará de que la función inicialice el valor antes de que salga. Así que TryParse tiene que establecer el parámetro param a algún valor incluso si la operación de análisis falló - El patrón TryXXX es un buen ejemplo de cuándo usar params: se introdujo Int32.TryParse porque las personas se quejaron del impacto de capturar excepciones para saber si el análisis fracasó. También lo más probable que haría en caso de que el análisis sintáctico tuviera éxito, es obtener el valor analizado. Usar un parámetro param significa que no tiene que hacer otra llamada al método para Parse.
Crear un tipo solo para devolver los valores me parece poco doloroso :-) Primero tendré que crear un tipo para devolver el valor, luego, en el método de llamada, he asignado el valor del tipo devuelto a la variable real que lo necesita.
Los parámetros de salida son simuladores de usar.
Creo que es útil para situaciones en las que necesitas devolver tanto un booleano como un valor, como TryParse, pero sería bueno si el compilador permitiera algo como esto:
bool isValid = int.TryParse("100", out int result = 0);
Definitivamente, los parámetros de salida están destinados a ser utilizados cuando tiene un método que necesita devolver más de un valor, en el ejemplo que publicó:
public void Do(int arg1, int arg2, out int result)
No tiene mucho sentido utilizar un parámetro out, ya que solo está devolviendo un valor, y ese método podría utilizarse mejor si elimina el parámetro out y pone un valor de retorno int:
public int Do(int arg1, int arg2)
Hay algunas cosas buenas sobre los parámetros de salida:
- Los parámetros de salida se consideran inicialmente no asignados.
- Todos los parámetros de salida deben asignarse definitivamente antes de que el método regrese, su código no se compilará si pierde una asignación.
En conclusión, básicamente intento usar params en mi API privada para evitar crear tipos separados para ajustar valores devueltos múltiples, y en mi API pública, solo los uso en métodos que coinciden con el patrón TryParse.
Me molesta que no pueda pasar nulo al parámetro out para las funciones TryParse.
Aún así, en algunos casos prefiero devolver un nuevo tipo con dos datos. Especialmente cuando no están relacionados en su mayor parte o una pieza solo es necesaria para una sola operación un momento después. Cuando necesito guardar el valor resultante de una función TryParse, realmente me gusta tener un parámetro out en lugar de una clase aleatoria ResultAndValue con la que tengo que lidiar.
Out es bueno cuando tienes una función TryNNN
y está claro que el parámetro de salida siempre se establecerá incluso si la función no tiene éxito. Esto le permite confiar en el hecho de que la variable local que declare se establecerá en lugar de tener que colocar cheques más adelante en su código contra nulo. (Un comentario a continuación indica que el parámetro podría establecerse como null
, por lo que es posible que desee verificar la documentación de la función que está llamando para asegurarse de que este sea el caso o no). Hace que el código sea un poco más claro y más fácil. leer. Otro caso es cuando necesita devolver algunos datos y un estado sobre la condición del método como:
public bool DoSomething(int arg1, out string result);
En este caso, el retorno puede indicar si la función tuvo éxito y el resultado se almacena en el parámetro de salida. Es cierto que este ejemplo es artificial porque puedes diseñar una forma en que la función simplemente devuelva una string
, pero obtienes la idea.
Una desventaja es que debe declarar una variable local para usarlos:
string result;
if (DoSomething(5, out result))
UpdateWithResult(result);
En lugar de:
UpdateWithResult(DoSomething(5));
Sin embargo, eso incluso puede no ser una desventaja, depende del diseño que está buscando. En el caso de DateTime, se proporcionan ambos medios (Parse y TryParse).
Sí, tiene sentido. Toma esto por ejemplo.
String strNum = "-1";
Int32 outNum;
if (Int32.TryParse(strNum, out outNum)) {
// success
}
else {
// fail
}
¿Qué podría devolver si la operación falla en una función normal con un valor de retorno? Definitivamente no podría devolver -1 para representar un error, porque entonces no habría diferencia entre el valor de falla de retorno y el valor real que se estaba analizando para comenzar. Es por eso que devolvemos un valor booleano para ver si tuvo éxito, y si lo hizo, entonces ya tenemos nuestro valor de "retorno" asignado de manera segura.
Si siempre creas un tipo, entonces puedes terminar con mucho desorden en tu aplicación.
Como se dijo aquí, un caso de uso típico es un Método TrySomething
en el que desea devolver un bool como indicador de éxito y luego el valor real. También me parece un poco más limpio en una declaración if: las tres opciones tienen aproximadamente la misma LOC de todos modos.
int myoutvalue;
if(int.TryParse("213",out myoutvalue){
DoSomethingWith(myoutvalue);
}
vs.
ParseResult<int> myoutvalue = int.TryParse("213");
if ( myoutvalue.Success ) {
DoSomethingWith(myoutvalue.Value);
}
vs.
int? myoutvalue = int.TryParse("213");
if(myoutvalue.HasValue){
DoSomethingWith(myoutvalue.Value);
}
En cuanto al "Por qué no se devuelve un Tipo Nullable": TryParse existe desde el Framework 1.x, mientras que los Nullable Types vienen con el 2.0 (Como requieren Generics). Entonces, ¿por qué romper innecesariamente la compatibilidad o comenzar a introducir inconsistencias entre TryParse en algunos tipos? Siempre puede escribir su propio Método de extensión para duplicar la funcionalidad ya existente (consulte Eric Lipperts Post en un tema no relacionado que incluye algún razonamiento detrás de hacer / no hacer cosas)
Otro caso de uso es si tiene que devolver múltiples valores no relacionados, aunque si lo hace, esto debería desencadenar una alarma de que su método posiblemente esté haciendo demasiado. Por otro lado, si su Método es algo así como una costosa base de datos o llamada de servicio web y desea almacenar en caché el resultado, puede tener sentido hacerlo. Claro, podría crear un tipo, pero nuevamente, eso significa un tipo más en su aplicación.