El tipo de una variable define cuanta memoria es requerida para almacerar sus datos. Tambien determina en que lugar de la memoria seran almacenados, la Pila o el Monticulo.
Los tipos estan divididos en dos categorias:
Tipos de Valor.
Tipos de Referencia.
Los tipos de valor requieren solo un segmento de memoria, el cual almacenara los datos.
Los tipos de referencia requieren dos segmentos de memoria:
El primero almacena los datos, y esta localizado en el monticulo.
El segundo es una referencia a donde los datos estan almacenados en el monticulo.
Sin embargo existen casos en que la referencia puede ser almacenada en el monticulo y no en la pila.
Cuando un programa se va a ejecutar, se reserva un espacio de memoria para almacenar su entorno, así como también los datos que va a utilizar. La cantidad de memoria que será requerida, donde y como será almacenada, dependerá del tipo de dato a declarar.
Al momento de requerir un espacio de memoria, se puden usar dos áreas distintas, las cuales son administradas de forma diferente. Estas dos áreas de memoria se llaman: La Pila (The Stack) y El Montículo o Montón (The Heap). Cada una con diferentes propósitos.
The Stack
La Pila es un arreglo de memoria que esta ordenado mediante el método LIFO (Last-in, First-out), donde el último elemento en entrar es el primero en salir.
Las características generales de la Pila son las siguientes:
Los datos solamente pueden ser añadidos o removidos desde la parte superior de la pila.
Agregar un elemento a la pila se denomina Pushing.
Remover un elemento de la pila se denomina Popping.
La pila almacena:
El entorno de ejecución del programa actual.
Parámetros pasados a los métodos.
Los valores de ciertos tipos de variables.
Otra forma de describir a La Pila es relacionándola con la forma en que está organizada una pila de cartones, los cuales se colocan uno encima del otro.
Por ejemplo, cuando se llama a un método, cada parámetro se coloca dentro de un cartón y luego el mismo se coloca en la parte superior de la pila. De la misma manera las variables locales del método, se colocan dentro de un cartón y luego se aplia sobre las cajas de parámetros existentes. Cuando el método finaliza, se remueven las cajas de variables y parámetros respectivamente.
El mismo ciclo de vida de los elementos almacenados en la pila ocurre cuando se declaran variables dentro de paréntesis, como por ejemplo en una sentencia While, For, etc.
Ejemplo 1
En el siguiente código del programa TheStackTest1 tipo consola, creamos tres variables:
namespace TheStackTest1
{
class Program
{
static void Main(string[] args)
{
int var1 = 20000;
short var2 = 15;
bool var3 = true;
}
}
}
Mediante las ventanas Call Stack y Locals de Visual Studio podemos ver la pila:
Como nos podemos dar cuenta, el primer elemento de la pila es el parámetro args del método Main, y el ultimo o el que se encuentra en la parte superior de la pila es la variable var3.
Para visualizar las ventana Call Stack y Locals, debemos ir al menú Debug, submenú Windows y elegir la opción Call Stack o Locals.
Ejemplo 2
En el siguiente código del programa TheStackTest2 tipo consola, creamos las mismas tres variables del Ejemplo 1, seguido de dos métodos más: Metodo1 y Metodo2.
using System;
namespace TheStackTest2
{
class Program
{
static void Main(string[] args)
{
int var1 = 20000;
short var2 = 15;
bool var3 = true;
Metodo1(var1, var2, var3);
}
static void Metodo1(int param1, short param2, bool param3)
{
decimal var4 = 5.05m;
Metodo2(var4);
}
static void Metodo2(decimal param4)
{
string var5 = "FIN";
Console.WriteLine(var5);
}
}
}
A continuación, se muestran las pilas de cada método:
Tal como se puede visualizar, al momento de llamar a un método, se añaden elementos a la pila (parámetros y variables) y una vez finalizados, se eliminan.
En estos ejemplos hemos usado tipos de datos predefinidos por C#, pero y ¿Si quisiéramos definir nuestras propias clases u objetos? Es aquí donde entra en juego el Montículo.
The Heap
El Montículo o Montón es un área de memoria donde se asignan fragmentos o espacios para almacenar ciertos tipos de objetos. A diferencia de la Pila, los datos pueden ser agregados y removidos en cualquier orden.
Las características generales del Montículo son las siguientes:
Todo objeto es almacenado en el Montón, mientras la referencia al objeto está en la Pila.
El tamaño del Montículo no es ilimitado, si al momento de crear un objeto no hay suficiente espacio, ocurrirá una excepción de tipo OutOfMemoryException, y el objeto no será creado.
Si tomamos nuevamente como referencia las cajas de cartón, en el caso del Montículo dichas cajas están esparcidas en el piso de una habitación sin un orden especifico.
Cuando un objeto es creado, el runtime busca una caja vacía del Montón de la habitación y lo coloca adentro. La referencia al objeto es almacenada en una variable local en la Pila. El runtime mantiene una pista del número de referencias a la caja donde se encuentra el objeto, ya que dos o más variables pueden hacer referencia a un mismo objeto. Cuando la última referencia al objeto desaparece, el runtime marca la caja como vacía o en desuso, y en este punto estará lista para que ese espacio de memoria quede disponible y se pueda almacenar otro objeto.
Los objetos almacenados en el Montículo no pueden ser eliminados de forma explícita. Por defecto, el Garbage Collector automáticamente limpia los objetos huérfanos cuando detecta que el código tiene un largo periodo de tiempo sin usarlos, con la ventaja que existan menos errores de memoria.
Observación
Cabe recalcar que el sistema se encarga automáticamente de la gestión de la Pila y el Montículo. Sin embargo, como desarrolladores es importante tener los conceptos claros de cómo funcionan estos mecanismos para la administración de datos en memoria.
En el desarrollo de software, una buena práctica antes de invocar a los miembros de un objeto es comprobar si el mismo fue instanciado correctamente. Para esto, comprobamos si su valor es diferente de null, si es verdadero, podemos usar sus miembros. De esta forma, evitamos que el compilador genere una excepción de tipo NullReferenceException, que detendría la ejecución del programa.
El Operador Condicional ?. es una forma abreviada de comprobar si el valor de un objeto es null, antes de tener acceso a sus miembros y hacer uso de ellos. La sintaxis es la siguiente:
Objeto?.Miembro
Que es equivalente a:
if (Objeto != null)
Objeto.Miembro
En el siguiente ejemplo tenemos una clase llamada Persona, con una sola propiedad: Nombre. El método MostrarNombre muestra en pantalla el nombre de la persona.
El operador condicional ?: es una forma abreviada de escribir una sentencia if-else simple. La sintaxis es la siguiente:
Condicion ? Expresion1 : Expresion2;
Que equivaldría a:
if (Condicion)
Expresion1;
else
Expresion2;
Para implementarlo, debemos de tener en cuenta lo siguiente:
La Condicion siempre debe de retornar un valor de tipo boolean.
Si la Condicion es true, Expresion1 se evalúa y se convierte en el resultado. Caso contrario Expresion2 se evalúa y se convierte en el resultado.
El tipo de dato de Expresion1 debe ser el mismo que el de Expresion2, caso contrario se debe realizar primero una conversión implícita de un tipo al otro.
Los operadores condicionales anidados siempre son evaluados desde la derecha. La expresión Condicion1 ? Expresion1 : Condicion2 ? Expresion2_1 : Expresion2_2 es equivalente a Condicion1 ? Expresion1 : (Condicion2 ? Expresion2_1 : Expresion2_2).
El operador condicional debe ser usado en sentencias de asignamiento, llamadas, incrementos, decrementos y expresiones de nuevos objetos que pueden ser usados como una sentencia.
Ejemplo
bool var = true;
string resultado = var == true ? "Verdadero" : "Falso";
Console.WriteLine(resultado);
En C# se pueden declarar varias variables en una sola sentencia. Sin embargo se deben seguir las siguientes pautas:
Todas las variables deben ser del mismo tipo.
Cada variable debe estar separada por una coma.
NO se pueden inicializar dos o mas variables con un mismo valor.
Ejemplo
//Cada variable tiene su propio valor inicial.int var1 = 10, var2 = 10, var3 = 20;
//var4 NO tiene inicializador, por lo tanto el compilador le asigna el valor default para tipos Boolean, False.bool var4, var5 = true;
//var7 NO tiene inicializador, por lo tanto el compilador le asigna el valor default para tipos double, 0.double var6 = 5.50, var7, var8 = 1.98;
//ERROR! variables de diferentes tipos.int var8, float var9;
C# proporciona 16 tipos de datos integrados o predefinidos para representar números enteros, decimales, de punto flotante, valores booleanos, caracteres, cadenas de caracteres, y otros tipos. De estos, 13 se denominan simples.
Los 13 tipos predefinidos simples incluyen:
Once tipos numéricos:
Ocho tipos enteros de varias longitudes, con y sin signo: sbyte, byte, short, ushort, int, uint, long y ulong.
Dos tipos de punto flotante: float y double.
Un tipo de mayor precisión llamado decimal, que a diferencia de float y double, puede representar números con fracciones exactas. Lo que lo hace adecuado para cálculos financieros, monetarios, operaciones aritméticas, etc.
Un tipo de caracter unicode, llamado char.
bool, Un tipo que representa dos valores, verdadero y falso.
Los 3 tipos restantes o no simples son:
object, que es el tipo base de todos los demás tipos.
string, el cual representa un arreglo de caracteres Unicode.
dynamic, el cual es usado para escribir assemblies en lenguajes dinámicos.
Tabla de Tipos Integrados Simples
Todos los tipos integrados simples del lenguaje C#, son alias de tipos .NET Framework predefinidos en el espacio de nombres System. Por ejemplo, int es un alias de System.Int32.
La siguiente tabla muestra todos los tipos de datos integrados simples.
Tipo C#
Intervalo
Tamaño / Precisión
Tipo .NET
Default
sbyte
De -128 a 127
Entero de 8 bits con signo
System.SByte
0
byte
De 0 a 255
Entero de 8 bits sin signo
System.Byte
0
short
De -32 768 a 32 767
Entero de 16 bits con signo
System.Int16
0
ushort
De 0 a 65.535
Entero de 16 bits sin signo
System.UInt16
0
int
De -2.147.483.648 a 2.147.483.647
Entero de 32 bits con signo
System.Int32
0
uint
De 0 a 4.294.967.295
Entero de 32 bits sin signo
System.UInt32
0
long
De -9.223.372.036.854.775.808 a 9.223.372.036.854.775.807
Entero de 64 bits con signo
System.Int64
0
ulong
De 0 a 18.446.744.073.709.551.615
Entero de 64 bits sin signo
System.UInt64
0
float
De ±1,5 x 10-45 a ±3,4 x 1038
7 dígitos
System.Single
0.0f
double
De ±5,0 × 10−324 a ±1,7 × 10308
15-16 dígitos
System.Double
0.0d
decimal
De ±1,0 x 10-28 to ±7,9228 x 1028
28-29 dígitos significativos
System.Decimal
0m
char
U+0000 a U+FFFF
Carácter Unicode de 16 bits
System.Char
\x0000
bool
Booleano
true, false
System.Boolean
false
Tabla de Tipos Integrados No Simples
Los tipos de datos integrados no simples son:
Tipo C#
Descripción
Tipo .NET
object
Es la clase base para todos los demás tipos, incluidos los tipos integrados simples.
System.Object
string
Una secuencia de caracteres Unicode.
System.String
dynamic
Es un tipo diseñado para ser usado con assemblies escritos en lenguajes dinámicos
No corresponde a un tipo .NET
Comentarios
El alias y su tipo .NET son intercambiables. Por ejemplo, puede declarar una variable de tipo entero de dos formas:
int varInt1 = 1; // Usando un alias
System.Int32 varInt2 = 2; // Usando un tipo .NET Framework
El Enlace de Datos o Data Binding, es una de las características más poderosas de WPF. Básicamente, permite establecer de una forma simple, la conexión entre las propiedades de un objeto Origen y un objeto Destino, con el objetivo de que sus valores permanezcan sincronizados.
De esta forma, el objeto destino será notificado cuando las propiedades del objeto origen cambien su valor, o viceversa. Gracias a este canal de comunicación, los cambios se ven reflejados automáticamente.
Esta funcionalidad, presenta las siguientes ventajas con respecto a los métodos tradicionales de programación:
Un número mayor de propiedades de elementos WPF implementan Data Binding.
Menos código de programación.
Flexibilidad al momento de interactuar con el usuario (validación, conversión, formateo, etc.)
Separación bien definida de la lógica del negocio de la interfaz de usuario, lo que permite implementar patrones como MVVM.
Componentes del Enlace de Datos
La clase que permite el enlace de datos es la System.Windows.Data.Binding, y por lo general tiene mínimo cuatro partes:
Un objeto origen: Que hace referencia al objeto de donde se obtienen los datos. Por lo general es una clase o entidad de la lógica del negocio, sin embargo, puede también ser una lista, un objeto ADO.NET, un control, o cualquier objeto CLR. El nombre del objeto origen se lo especifica en el objeto destino mediante la propiedad ElemenName, de la clase Binding.
Un objeto destino: Que debe ser del tipo DependencyObject, y por lo general es un control WPF. Puesto que todos los controles heredan de la clase UIElement y esta a su vez de DependencyObject, los mismos aplican perfectamente como destino del enlace.
Una propiedad destino: Que debe ser una Propiedad de Dependencia o DependencyProperty. La mayoría de las propiedades UIElement son DependencyProperty, y solo los objetos DependencyObject pueden definir propiedades de dependencia.
Una ruta: Que representa el valor o la propiedad del objeto origen que se va a enlazar. Se la especifica en el objeto destino mediante la propiedad Path de la clase Binding.
Ejemplo
En el siguiente ejemplo tenemos un control TextBlock (Objeto Destino) enlazado a un control TextBox (Objeto Origen), cuya propiedad a sincronizar es la Text.
El resultado de este ejemplo básico es que todo texto que se ingrese en el TextBox (Objeto origen), se verá reflejado en el TextBlock (Objeto destino).
La dirección o Address, es la primera parte del ABC de WCF. La misma define donde se encuentra ubicado el servicio, puede ser un servidor remoto en internet, en la intranet, o una misma PC.
La dirección contiene toda la información necesaria para que los clientes puedan conectarse a él, o los endpoints del servicio. Está compuesta de las siguientes partes:
[transporte]://[maquina][:puerto]/[ruta]
Transporte
El protocolo de transporte (Transport Protocol o Transport Scheme) es el medio de comunicación de él, o los endpoints del servicio. WCF soporta los siguientes Esquemas de Transporte:
HTTP/HTTPS.
TCP.
IPC.
MSMQ.
Service Bus.
WebSocket.
UDP.
Direcciones HTTP
Las direcciones HTTP usan como transporte el protocolo http o http seguro (https). Es el transporte preferido para la web.
El formato de las direcciones HTTP es:
http://[nombre de dominio o nombre de servidor o dirección IP][:puerto]/[ruta]
Algunas ejemplos de direcciones HTTP:
http://midominio:8001/MiServicio
http://localhost:8001
http://172.217.4.4:8001/publico/MiServicio
Si no se especifica el puerto, el default es el 80 y 443 para HTTPS.
https://midominio.com/MiServicio
Direcciones TCP
Como su nombre lo indica, las direcciones TCP usan como transporte el protocolo TCP. Por lo general incluyen el número de puerto al cual conectarse.
net.tcp://localhost:8001/MiServicio
Cuando el puerto no es especificado, el default es el 808.
net.tcp://localhost/MiServicio
Es posible que dos direcciones TCP del mismo host, compartan un mismo puerto:
Las direcciones Inter Process Communication (IPC) usan net.pipe como transporte. Estos servicios solo pueden comunicarse con los clientes de la misma máquina. Tampoco es permitido que dos direcciones compartan el mismo nombre.
net.pipe://localhost/MyPipe
Direcciones MSMQ
Las direcciones Microsoft Message Queue (MSMQ) usan como transporte net.msmq. Se debe especificar el nombre de la cola y a su vez su tipo. Si se omite, por default la cola es pública.