jueves, 19 de septiembre de 2013

Variables y Parámetros en C# - Parte 1

Tabla de Contenido

0. Introducción
1. Definición de Variable
2. Stack and Heap
3. Política de Asignación de Valores a Variables
4. Valores por Defecto de Variables
5. Conclusiones
6. Glosario
7. Referencias

Introducción

En esta oportunidad les hablaré acerca de un par de elementos importantes en la creación de estructuras más complejas (funciones, eventos, delegados, instancias de objetos, contenedores de valores, entre muchos más): las variables y los parámetros. En el caso de las variables, las podemos ver como locaciones (espacios) de memoria necesarios para guardar un valor durante un tiempo indeterminado (el tiempo que tarde en finalizar la lógica de un método, por ejemplo). Por otro lado, también presentaré el uso de los parámetros en la especificación de un método. Éstos se convierten en los receptáculos requeridos por un método para ejecutarse con la información necesaria de entrada y poder operar según su implementación lógica.



Este tema estará dividido en dos partes: en la primera haré un recorrido por el stack y el heap, asignación de valores a variables antes de ser usadas, valores por defecto en variables de tipos por valor y por referencia.

Ya en la segunda parte, entraremos más a fondo a tratar los parámetros con subtemas como: pasar argumentos por valor, los modificadores ref, out, las implicaciones de pasar por referencia, parámetros opcionales, argumentos con nombres de variables.

1. Definición de Variable

En su definición básica, podemos ver a una variable como un receptáculo en donde podemos asignar (colocar, posicionar) un valor (numérico, caracteres, cadenas de caracteres, o en casos más complejos, la referencia a una instancia de una clase de objeto, etc.).

Por otro lado, si vemos el concepto de variable algebraica, esta queda definida de la siguiente manera (en inglés) [1]:

Understanding that a variable is just a symbol that can represent different values.
La anterior definición, se puede entender como un elemento lexicográfico o literal que es capaz de representar valores diferentes. Para particularizar, los valores son de diferente naturaleza (como los que se mencionaron arriba: números enteros, reales, cadenas de caracteres, etc.)

También es interesante la definición oficial en Wikipedia [2]:
In computer programming, a variable is a storage location and an associated symbolic name (an identifier) which contains some known or unknown quantity or information, a value.
Diferentes representaciones o formas de una variable en C# [3]:
  • Local
  • Parámetro (value, ref, o out) (lo veremos con más detalle en la segunda entrega).
  • Campo (de instancia, o estático).
  • Elemento de un posición en un arreglo.
Otra consideración importante acerca de las variables son las partes que le integran:
  • Nombre (identificador): es el nombre literal compuesto a partir de caracteres permitidos por la especificación del lenguaje de programación; y que puede ser parte de una expresión, argumento de un método, etc.
  • Valor: es la información o dato al que el identificador referencia. Este valor puede ser cambiado en el ciclo de vida de la ejecución del programa.

2. Stack and Heap

Desde las definiciones anteriores de variable, puede resultar la pregunta: ¿Dónde se almacenan las variables? Estos dos nuevos términos: stack y heap contestan esta cuestión, pues son las estructuras en donde residen las variables (lo mismo sucede con las constantes). En [3], se aclara que estas dos estructuras se diferencian por la vida útil diferente a nivel de semántica.

También, estas dos estructuras, se pueden definir como medios para la asignación de memoria. A continuación se describen las características fundamentales de éstas, además, de la inclusión de ejemplos en código fuente C# para aclarar y afianzar más el concepto básico.

Stack

En [4] se define stack como: en arquitecturas de computación, un stack no es más que una región de memoria en donde los datos son removidos de acuerdo al modo último-en-entrar-primero-en-salir (o inglés, LIFO - Last-In-First-Out [5]), es decir, que es posible extraer el último elemento de la parte superior de la pila, los demás no son accesibles. Es por eso, que a esta estructura es funcionalmente limitada, pero útil para infinidad de aplicaciones de almacenamiento de datos en tiempo de ejecución.

Por otro lado, de acuerdo a [3], el stack es un bloque de memoria para el almacenamiento de variables y parámetros. El número de elementos en esta estructura crece y decrece según el número de inicio de funciones, y salidas, también.

Para aterrizar el concepto, veamos el siguiente fragmento de código que corresponde a la versión recursiva de la función Factorial [6]:

static int Factorial (int x)
{
if (x == 0)
{
return 1;
}
else
{
return x * Factorial (x-1)
}
}

El paso recursivo, inmediatamente del cuerpo de else invoca nuevamente a la función Factorial (llamada recursiva), por lo tanto en el stack se crea una nueva variable de tipo int, al contrastar, cuando la función alcance el paso base es decir cuando se cumpla la condición x == 0  los elementos almacenados en el stack son eliminados debido a su inutilidad en las llamadas anteriores.

Y aún, para que quede más explicito, en el siguiente diagrama se esquematiza cómo el stack se va cargando de las llamadas recursivas de las funciones, y a medida que cada una de estás finaliza su trabajo (implementación lógica), se va desocupando hasta llegar, en un caso particular, a la primera llamada:
Stack de llamadas de una función recursiva.
Stack de llamadas de una función recursiva [8].

Heap

El heap al igual que el stack, es un bloque de memoria usado para almacenar objetos (i.e., instancias de tipos por referencia [3]). Cuando el programador declara e inicializa un objeto de una clase en particular, este objeto es almacenado en esta región de memoria especial, finalmente, retorna la referencia hacia esa locación y queda señalada por el identificador(es) declarado.

El tamaño del heap, según [3], va creciendo a medida que el programador crea instancias de clases de objetos, de forma paralela la CLR a través del recolector de basura (garbage collector) irá desocupando la memoria utilizada por objetos innecesarios (esta es una forma eficiente y automática que posee .NET Framework para hacer más productiva la actividad del programador).

Paso al siguiente ejemplo para hacer más clara la definición anterior:
En este ejemplo, en particular en la línea 8 se crea un objeto StringBuilder [9] referenciado por la variable ref1  y en la línea 9 se escribe en la salida estándar su contenido ("objeto no. 1"). Finalizada esta última instrucción, el objeto es candidato para la recolección de basura, debido a que no es usado en instrucciones anteriores.

Ocurre lo contrario con la variable de tipo por referencia ref2, es decir que no se convierte en candidato para recolección de basura, a razon de que ref3 es usado en la línea 16.

También vale agregar que los campos de tipo por valor y por referencia declarados como elementos estructurales en una clase de objeto, por ejemplo, también residirán en la memoria heap.

Nota importante desde [3]:
You can't explicitly delete objects in C#, as you can in C++. An unreferenced object is eventually collected by the garbage collector.
El heap, también es capaz de almacenar campos estáticos, además, de constantes [3].

3. Política de Asignación de Valores a Variables

[3]: C# hace cumplir la política asignación definitiva. Es decir, que en la práctica el programador no tiene permitido usar una variable, si ésta no ha sido declarada. La definición de asignación de valores a variables tiene las siguientes implicaciones:
  • A las variables locales se les debe asignar un valor antes de ser usadas en una expresión, llamada a función, incremento unitario del valor (en el caso de variables numéricas).
  • Para los argumentos de una función, éstos deben ser especificados en el momento de invocar la función (al menos de que el argumento haya sido marcado como opcional).
  • Las demás variables (como los campos y los elementos de un arreglo), la CLR los inicializa en tiempo de ejecución.
Ejemplos de mal uso de las políticas asignación:

static int Main (int x)
{
int x;
Console.WriteLine (x); // Error en tiempo de compilación.
}

Otro ejemplo interesante consiste en la inicialización automática de los valores de los elementos de un arreglo, como sigue:

static int Main (int x)
{
int[] ints = new int[2];
Console.WriteLine (ints[0]); // 0
}

El siguiente ejemplo demuestra el valor de inicialización por defecto de un campo static de una clase:

4. Valores por Defecto de Variables

En la Tabla 1 se presenta los valores por defecto de tipos de dato por valor y por referencia:
Valores por defecto de variables.
Tabla 1. Valores por Defecto de Variables de Tipo por Valor y por Referencia.

[3] dice:
You can obtain the default value for any type using the default keyword.
decimal d = default (decimal);

5. Conclusiones

Se ha reconocido la importancia de los elementos estructurales lógicos conocidos como variables. Éstos nos ayudan a almacenar valores de datos (numéricos, de caracteres, cadenas de caracteres, referencias a instancias de una clase de objeto, etc.). Se han presentando estructuras de locación de memoria: stack y heap necesarias para localizar variables, parámetros de llamadas a funciones (stack), y heap para las variables por referencia. También se hizo notar las políticas básicas de asignación de valores a variables de tipos, como también la presentación del resumen de los valores por defecto de las variables.

6. Glosario

- Estático
- Heap
- LIFO
- Stack
- Variable

7. Referencias

[1] What is a variable? | Variables and expressions | Khan Academy - https://www.khanacademy.org/math/algebra/introduction-to-algebra/variable-and-expressions/v/what-is-a-variable
[2] Variable (computer science) - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Variable_(computer_science)
[3] C# 5.0 in a Nutshell by Joseph Albahari and Ben Albahari. Copyright 2012 Joseph Albahari and Ben Albahari, 978-1-449-32010-2.
[4] Stack-based memory allocation - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/Stack-based_memory_allocation
[5] LIFO (computing) - Wikipedia, the free encyclopedia - http://en.wikipedia.org/wiki/LIFO_(computing)
[6] Factorial - Wikipedia - https://es.wikipedia.org/wiki/Factorial
[7] Función recursiva - Wikipedia - https://es.wikipedia.org/wiki/Funci%C3%B3n_recursiva
[8] senocular.com - Asynchronous ActionScript Execution - http://www.senocular.com/flash/tutorials/asyncoperations/?page=2

2 comentarios:

  1. Buenas tardes.

    Le comento que en el apartado del Stack escribe:

    "primero-en-entrar-primero-en-salir (o inglés, LIFO - Last-In-First-Out [5])"

    Sería: "último-en-entrar-primero-en-salir".

    Un saludo y muchas gracias por su blog.

    ResponderEliminar

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