sábado, 27 de septiembre de 2014

Pregunta C# (10 de 20): ¿Cuál es la Diferencia entre las Construcciones ref y out?

Índice

0. Introducción
1. Contexto
2. Construcción ref
3. Construcción out
4. Recursos
5. Conclusiones
6. Glosario
7. Literatura & Enlaces

0. Introducción

Con esta pregunta C# llegamos a la mitad de la serie de preguntas del curso de Twenty C# Questions Explained de la Microsoft Virtual Academy: número 10. En esta oportunidad vamos a comprender las diferencias entre dos construcciones (o palabras clave) de C#: ref y out. Crearemos varios ejemplos de uso para comprender cuándo debemos usar ref en lugar de out.Veremos cómo estas construcciones son clave en el diseño de métodos bajo el patrón TryXXX. Esto último con el propósito de demostrar una de sus utilidades prácticas en la biblioteca base de clases de .NET y los propias implementaciones que pudiera crear el mismo programador/desarrollador.

1. Contexto

El lenguaje de programación C# cuenta con mecanismos excepcionales para crear implementaciones para un sinnúmero de contextos de solución de problemas, y en esta oportunidad presentaremos dos construcciones (o palabras clave) que permiten a un método manipular una variable de cualquier de los tipos naturales (i.e., por valor o por referencia), se trata de las construcciones sintácticas y semánticas:
  • ref, y 
  • out
Estas dos construcciones, en resumen (ya nos extenderemos en secciones próximas), constituyen los modos en los que son pasadas las variables a un método, y cómo deben ser asignadas dentro del método invocado.


En las siguientes secciones veremos cómo podemos usarlas para manipular valores dentro la implementación lógica de un método, además, de la generación de la posibilidad para la conversión de variables pasadas con estos calificadores de parámetros en múltiples valores de retorno.



Por otro lado, el seguimiento del patrón TryXXX es parte fundamental en muchos métodos de conversión de tipos por valor en la biblioteca de base de clases. En ejemplos de uso en la sección 3 quedará evidenciado el uso tanto de métodos de la BCL como también algunos que creemos nosotros mismos para poner en evidencia su utilidad y sobretodo la alternativa viable que presenta frente al uso de excepciones.

2. La Construcción ref

La palabra clave o construcción ref [5] deja que un argumento sea pasado por referencia, en lugar de por valor [5]. El efecto que tiene esta palabra clave dentro de la implementación de un método consiste en mantener varias referencias sobre un único valor dentro y fuera del método que se invoca. Pasemos a un primer ejemplo para poner en prueba el concepto anterior:

En la línea 11 declaramos una variable entera de 32 bits (int) y le asignamos la literal entera 11. Mostramos el valor actual de la variable i con la invocación del método WriteLine de Console antes de invocar el método ModificarTipoPorValor (líneas 29-32). Sobre la línea 19, invocamos al método ModificarTipoPorValor; notemos como además de especificar el argumento i anteponemos la palabra clave ref; esta especificación es la que permite que una variable, en especial, de tipo por valor sea pasada como referencia. Después (línea 22) mostramos en pantalla el valor de la variable i en la salida estándar.

Compilación:


  1. csc /target:exe UsoRef.cs

Ejecuión assembly:


  1. .\UsoRef.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly UsoRef.exe
Figura 2. Ejecución assembly UsoRef.exe.


Debe quedar claro que antes de pasar una variable como argumento de un método con un parámetro calificado con ref, esta variable debe ser inicializada de lo contrario se generará el error de compilación CS0165 [6]. A tener en cuenta también, que la asignación de un valor o modificación de la variable pasada como referencia es opcional cuando se trata de este calificador de parámetro.


Vale agregar la nota acerca de los conceptos de tipos por referencia y los argumentos pasados como referencia explicitada en [6]:
Nota acerca de los tipos por referencia y los argumentos por referencia
Figura 2. Nota acerca de los tipos por referencia y los argumentos por referencia [6].

3. La Construcción out

El calificador de parámetro de método out [5] permite que los argumentos de un método se pasen por referencia, y no por valor. Sin embargo, hay que considerar la siguiente diferencia:
«This is like the ref keyword, except that ref requires that the variable be initialized before it is passed.»
Aquí encontramos una de las diferencias principales entre out y ref que consiste en reglas distintas de asignación. En resumen:
  • El uso del calificador out no requiere que la variable sea inicializada antes de ser pasada al método con la especificación correspondiente.
  • Mientras que una variable que requiera ser pasada como argumento de un parámetro calificado con ref si resulta mandatorio inicializar esa variable con un valor o referencia específicos.
Antes de pasar a la ejemplificación de este calificador de parámetro, es importante tener en cuenta los casos de sobrecarga donde el compilador de C# no distingue entre ref y out:

public void Metodo(out int i) { }
public void Metodo(ref int i) { }


E intentar esto hará que el compilador de C# genere el error CS0663 [8]. Cuando lo anterior no es posible, la siguiente especificación de métodos sobrecargados, sí:


public void Metodo(out int i) 
{
i = 5;
}

public void Metodo(int i) { }


Creemos otro ejemplo, similar al de la sección 2, pero esta vez pondremos en uso el calificador de parámetro out:


Archivo UsoOut.cs [enlace alternativo]:

Empezamos con la línea 11 en donde declaramos la variable entera de 32 bits i. (Nótese que la variable no fue inicializada.) Luego, en la línea 14, invocamos al método ModificarTipoPorValor y usamos la palabra clave out dispuesta antes del identificador i. Dentro del método ModificarTipoPorValor asignamos (y estamos obligado a hacerlo) la literal entera 24 a la variable i. (Nota: el nombre del parámetro no debe coincidir con el nombre del argumento, en este caso i.) Finalmente invocamos al método WriteLine para mostrar el valor de la variable i después de invocar el método ModificarTipoValor.

Compilación:


  1. csc /target:exe UsoOut.cs

Ejecución assembly:


  1. .\UsoOut.exe

> Prueba de ejecución (ideone.com).

> Prueba de ejecución (local):
Ejecución assembly UsoOut.exe
Figura 3. Ejecución assembly UsoOut.exe.

Hasta este punto ya debemos tener claro tanto conceptual como teóricamente las diferencias existentes entre ref y out, sin embargo ha otros puntos que deben ser tratados, y son los siguientes:


Se puede pensar en los parámetros out como si trataran de valores de retorno adicionales que pueden ser emitidos desde la implementación de un método. Y esto hace que se manifieste el uso del patrón de diseño de método TryXXX.



El patrón TryXXX [1] se presenta como una alternativa al manejo o manipulación de excepciones. Se trata de un modo semántico alternativo para el tratamiento de errores en la ejecución de una función o de un elemento de un programa. Podríamos pensar en este patrón como una formalización al clásico enfoque de codificación de errores a través de caracteres alfanuméricos, por ejemplo:
  • 0: el programa ha finalizado satisfactoriamente.
  • 1: ha un corrido un error y el programa debió cerrarse sin terminar su ciclo de ejecución.
  • 2: ocurrió un desbordamiento de memoria.
  • 3: no hay memoria de sistema suficiente para localizar los datos del programa.
empero, con este nuevo patrón sólo recurrimos a dos posibles estados de correctitud (calidad de correcto):
  • true: la ejecución de la función (i.e., un método) finalizó correctamente.
  • false: la función ha fallado.
Ya mencionamos que este enfoque para el diseño de métodos se presenta como una alternativa ante las excepciones; sin embargo, podemos diseñar nuestros tipos usando ambos enfoques. Por ejemplo:
  1. public int Convertir (string valor);
  2. public bool TryConvertir (string valor, out int valorRetorno);
Para el caso (1), en caso de que la conversión falle con la invocación a Convertir se lanzará una excepción relacionada con el problema ocurrido (e.g.FormatException [5]). Mientras que con el método TryConvertir si algo falla, este retorna false.


En la biblioteca base de clases del Framework .NET encontramos un sinnúmero de estructuras y clases que implementan este patrón para ofrecer la alternativa de manejo de errores. En el caso de las estructuras básicas que representan tipos de datos numéricos podemos mencionar:
  • Int32.TryParse [6] / Int32.Parse
  • Boolean.TryParse [7] / Boolean.Parse
  • Decimal.TryParse [8] / Decimal.Parse
  • Int64.TryParse [9] / Int64.Parse
  • Dictionary<TKey,TValue>.TryGetValue [8] / Versión con indexer
  • Uri.TryCreate [10] 
Para no extendernos más sobre este tópico, sugiero la lectura del artículo Uso Excepciones.

4. Recursos

Este es el vídeo en donde el equipo de expertos (Gerry O'Brein y Paul Pardi) de MVA responden y explican esta pregunta:

5. Conclusiones

Aprendimos que el calificador ref permite que un argumento sea pasado por referencia, y no por valor, y permite que dentro sea posible alterar el valor de la variable marcada como ref. Tal argumento debe ser inicializado antes de ser pasado como argumento. Igualmente, comprendimos que la construcción out también permite pasar un argumento por referencia, y no por valor. Antes de pasar un argumento no es necesario que este sea inicializado. En la próxima pregunta C# aprenderemos a encriptar y desencriptar un objeto tipo string.

6. Glosario


  • BCL
  • Calificador
  • Clase
  • Método
  • out
  • Parámetro
  • ref

7. Literatura & Enlaces

[1]: Twenty C# Questions Explained - http://www.microsoftvirtualacademy.com
[2]: c# - Difference between ref and out parameters in .NET - Stack Overflow - http://stackoverflow.com/questions/135234/difference-between-ref-and-out-parameters-in-net
[3]: C# and the difference between out and ref - http://geekswithblogs.net/ftom/archive/2008/09/10/c-and-the-difference-between-out-and-ref.aspx
[4]: out parameter modifier (C# Reference) - http://msdn.microsoft.com/en-us/library/vstudio/ee332485(v=vs.100).aspx
[5]: ref (C# Reference) - http://msdn.microsoft.com/en-us/library/vstudio/14akc2c7(v=vs.100).aspx
[6]: Compiler Error CS0165 - http://msdn.microsoft.com/en-us/library/4y7h161d.aspx
[7]: Excepciones en C# - Parte 2: Uso de Excepciones | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/07/excepciones-en-csharp-parte-2-uso-de-excepciones.html
[8]: Compiler Error CS0663 - http://msdn.microsoft.com/en-us/library/cyfs0yyd(v=vs.90).aspx


J

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.