DPA – Tipos de datos.

 

Desarrollo Profesional de Aplicaciones


Tipos de datos


Tipos básicos
El sistema unificado de tipos. El tipo Object
Cadenas de caracteres
Vectores y matrices
Estructuras
Enumeraciones

Cuando definimos un objeto debemos especificar su tipo. El tipo determina qué valores puede almacenar ese objeto (clase y rango) y las operaciones que pueden efectuarse con él.

Como cualquier lenguaje de programación, C# proporciona una serie de tipos predefinidos (int, byte, char, string, …) y mecanismos para que el usuario cree sus propios tipos (class y struct).

La estructura de tipos de C# es una gran novedad ya que establece una relación jerárquica entre éstos, de manera que todos los tipos son clases y se construyen por herencia de la clase base Objet. Esta particularidad hace que la creación y gestión de tipos de datos en C# sea una de las principales novedades y potencialidades del lenguaje.

Tipos básicos

Los tipos de datos básicos son los tipos de datos más comúnmente utilizados en programación.

Los tipos predefinidos en C# están definidos en el espacio de nombres System, que es el espacio de nombres más numeroso (e importante) de la plataforma .NET. Por ejemplo, para representar números enteros de 32 bits con signo se utiliza el tipo de dato System.Int32 y a la hora de crear un objeto a de este tipo que represente el valor 2 se usa la siguiente sintaxis:

   System.Int32 a = 2;

Al ser un tipo valor no se utiliza el operador new para crear objetos System.Int32, sino que directamente se indica el literal que representa el valor a crear, con lo que la sintaxis necesaria para crear entero de este tipo se reduce considerablemente. Es más, dado lo frecuente que es el uso de este tipo también se ha predefinido en C# el alias int para el mismo, por lo que la definición de variable anterior queda así de compacta:

   int a = 2;

System.Int32 no es el único tipo de dato básico incluido en C#. En el espacio de nombres System se han incluido los siguientes tipos:

C# Tipo en System Características Símbolo
sbyte System.Sbyte entero, 1 byte con signo
byte System.Byte entero, 1 byte sin signo
short System.Short entero, 2 bytes con signo
ushort System.UShort entero, 2 bytes sin signo
int System.Int32 entero, 4 bytes con signo
uint System.UInt32 entero, 4 bytes sin signo U
long System.Int64 entero, 8 bytes con signo L
ulong System.ULong64 entero, 8 bytes sin signo UL
float System.Single real, IEEE 754, 32 bits F
double System.Double real, IEEE 754, 64 bits D
decimal System.Decimal real, 128 bits (28 dígitos significativos) M
bool System.Boolean (Verdad/Falso) 1 byte
char System.Char Carácter Unicode, 2 bytes ´ ´
string System.String Cadenas de caracteres Unicode ” “
object System.Object Cualquier objeto (ningún tipo concreto)

Los tipos están definidos de manera muy precisa y no son dependientes del compilador o de la plataforma en la que se usan.

La tabla anterior incorpora la columna Símbolo para indicar cómo debe interpretarse un literal. Por ejemplo, 28UL debe interpretarse como un entero largo sin signo (ulong).

Todos los tipos enumerados son tipos valor, excepto string (que no debe confundirse con un vector de caracteres) y Object que son tipos referencia. Recuerde, no obstante, que los datos string se comportaban como un tipo valor ante la asgnación.

El sistema unificado de tipos. El tipo Object

En C# desaparecen las variables y funciones globales: todo el código y todos los datos de una aplicación forman parte de objetos que encapsulan datos y código (como ejemplo, recuerde cómo Main() es un método de una clase). Como en otros lenguajes orientados a objetos, en C# un tipo puede incluir datos y métodos. De hecho, hasta los tipos básicos predefinidos incluyen métodos, que, como veremos, heredan de la clase base object, a partir de la cual se construyen implícita o explícitamente todos los tipos de datos. Por ejemplo:

   int i = 10;
   string c = i.ToString();

e incluso:

   string c = 10.ToString();

Esta manera por la que podemos manipular los datos básicos refleja la íntima relación entre C# y la biblioteca de clase de .NET. De hecho, C# compila sus tipos básicos asociándolos a sus correspondientes en .NET; por ejemplo, hace corresponder al tipo string) con la clase System.String, al tipo int) con la clase System.Int32, etc. Así, se confirma que todo es un objeto en C# (por si aún había alguna duda).

Aunque no comentaremos todos los métodos disponibles para los tipos básicos, destacaremos algunos de ellos.

  • Todos los tipos tienen un método ToString() que devuelve una cadena (string) que representa su valor textual.
  • char tiene propiedades acerca de su contenido (IsLetter, IsNumber, etc.) además de métodos para realizar conversiones (ToUpper(), ToLower(), etc.)
  • El tipo básico string dispone, como puede suponerse, de muchos métodos específicos, aún más que la clase string de la biblioteca estándar de C++.

Algunos métodos estáticos y propiedades particularmente interesantes son:

  • Los tipos numéricos enteros tipos tienen las propiedades MinValue y MaxValue (p.e. int.MaxValue).
  • Los tipos float y double tienen la propiedad Epsilon, que indica el mínimo valor positivo que puede representarse en un dato de su tipo (p.e. float.Epsilon).
  • Los tipos float y double tienen definidos los valores NaN (no es un número, no está definido), PositiveInfinite y NegativeInfinity, valores que son pueden ser devueltos al realizar ciertos cálculos.
  • Muchos tipos, incluyendo todos los tipos numéricos, proporcionan el método Parse() que permite la conversión desde un dato string (p.e. double d = double.Parse("20.5"))

Conversiones de tipos

Una conversión de tipo (casting) puede ser implícita o explícita.

Implícitas Explícitas
Ocurren automáticamente Requieren un casting
Siempre tienen éxito Pueden fallar
No se pierde información Se puede perder información
int x = 123456;
long y = x;            // implicita
short z = (short) x;   // explicita (riesgo)

float f1 = 40.0F;
long  l1 = (long)  f1;   // explicita (riesgo por redondeo)
short s1 = (short) l1;   // explicita (riesgo por desbordamiento)
int   i1 = s1;           // implicita, no hay riesgo
uint  i2 = (uint) i1;    // explicita (riesgo de error por signo)

En C# las conversiones de tipo (tanto implícitas como explícitas) en las que intervengan tipos definidos por el usuario pueden definirse y particularizarse.

Recordemos que pueden emplearse los operadores checked y unchecked para realizar conversiones (y operaciones aritméticas) en un contexto verificado: el entorno de ejecución .NET detecta cualquier situación de desbordamiento y lanza una excepción OverFlowException si ésta se manifiesta.

El tipo object

Por el sistema unificado de tipos de C#, todo es un objeto. C# tiene predefinido un tipo referencia llamado object y cualquier tipo (valor o referencia, predefinido o definido por el usuario) es en última instancia, de tipo object (con otras palabras puede decirse que hereda todas las características de ese tipo).

El tipo object se basa en System.Object de .NET Framework. Las variables de tipo object pueden recibir valores de cualquier tipo. Todos los tipos de datos, predefinidos y definidos por el usuario, heredan de la clase System.Object. La clase Object es la superclase fundamental de todas las clases de .NET Framework; la raíz de la jerarquía de tipos.

Normalmente, los lenguajes no precisan una clase para declarar la herencia de Object porque está implícita.

Por ejemplo, dada la siguiente declaración:

   object o;

todas estas instrucciones son válidas:

   o = 10;  // int
   o = "Una cadena para el objeto";  // cadena
   o = 3.1415926;    // double
   o = new int [24]; // vector de int
   o = false; // boolean

Dado que todas las clases de .NET Framework se derivan de Object, todos los métodos definidos en la clase Object están disponibles en todos los objetos del sistema. Las clases derivadas pueden reemplazar, y de hecho reemplazan, algunos de estos métodos, entre los que se incluyen los siguientes:

  • public void Object() Inicializa una nueva instancia de la clase Object. Los constructores llaman a este constructor en clases derivadas, pero éste también puede utilizarse para crear una instancia de la clase Object directamente.
  • public bool Equals(object obj) Determina si el objeto especificado es igual al objeto actual.
  • protected void Finalize() Realiza operaciones de limpieza antes de que un objeto sea reclamado automáticamente por el recolector de elementos no utilizados.
  • public int GetHashCode() Sirve como función hash para un tipo concreto, apropiado para su utilización en algoritmos de hash y estructuras de datos como las tablas hash.
  • public string ToString() Devuelve un objeto string que representa al objeto actual. Se emplea para crear una cadena de texto legible para el usuario que describe una instancia de la clase. La implementación predeterminada devuelve el nombre completo del tipo del objeto Object.En el último ejemplo, si cada vez que asignamos un valor a o ejecutamos Console.WriteLine (o.ToString()) o simplemente Console.WriteLine (o); obtendremos este resultado:
       10
       Una cadena para el objeto
       3,1415926
       System.Int32[]
       False
    
  • public Type GetType() Obtiene el objeto Type que representa el tipo exacto en tiempo de ejecución de la instancia actual.En el último ejemplo, si cada vez que asignamos un valor a o ejecutamos Console.WriteLine (o.getType()) obtendremos este resultado:
       System.Int32
       System.String
       System.Double
       System.Int32[]
       System.Boolean
    
  • protected object MemberwiseClone() Crea una copia superficial del objeto Object actual. No se puede reemplazar este método; una clase derivada debe implementar la interfaz ICloneable si una copia superficial no es apropiada. MemberwiseClone() está protegido y, por tanto, sólo es accesible a través de esta clase o de una clase derivada. Una copia superficial crea una nueva instancia del mismo tipo que el objeto original y, después, copia los campos no estáticos del objeto original. Si el campo es un tipo de valor, se realiza una copia bit a bit del campo. Si el campo es un tipo de referencia, la referencia se copia, pero no se copia el objeto al que se hace referencia; por lo tanto, la referencia del objeto original y la referencia del punto del duplicado apuntan al mismo objeto. Por el contrario, una copia profunda de un objeto duplica todo aquello a lo que hacen referencia los campos del objeto directa o indirectamente.

Polimorfismo -boxing y unboxing-

Boxing (y su operación inversa, unboxing) permiten tratar a los tipos valor como objetos (tipo referencia). Los tipos de valor, incluidos los struct y los predefinidos, como int, se pueden convertir al tipo object (boxing) y desde el tipo object (unboxing).

La posibilidad de realizar boxing permite construir funciones polimórficas: pueden realizar una operación sobre un objeto sin conocer su tipo concreto.

void Polim(object o) 
{
  Console.WriteLine(o.ToString());   
}
...
Polim(42);
Polim("abcd");
Polim(12.345678901234M);
Polim(new Point(23,45));

Boxing

Boxing es una conversión implícita de un tipo valor al tipo object. Cuando se realiza boxing de un valor, se asigna una instancia de objeto y se copia el valor en el nuevo objeto.

Por ejemplo, considere la siguiente declaración de una variable de tipo de valor:

   int i = 123;

La siguiente instrucción aplica implícitamente la operación de boxing sobre la variable i:

   object o = i;

El resultado de esta instrucción es crear un objeto o en la pila que hace referencia a un valor del tipo int alojado en el heap. Este valor es una copia del valor del tipo de valor asignado a la variable i. La diferencia entre las dos variables, i y o se muestra en la siguiente figura: En definitiva, el efecto del boxing es el de cualquier otro tipo de casting pero:

  • el contenido de la variable se copia al heap
  • se crea una referencia a ésta copia

Aunque no es necesario, también es posible realizar el boxing explícitamente como en el siguiente ejemplo:

   int i = 123;
   object o = (object) i;

El siguiente ejemplo convierte una variable entera i a un objeto o mediante boxing. A continuación, el valor almacenado en la variable i se cambia de 123 a 456. El ejemplo muestra que el objeto mantiene la copia original del contenido, 123.

// Boxing de una variable int
using System;
class TestBoxing  
{
   public static void Main() 
   {
      int i = 123;
      object o = i;  // boxing implicito
      i = 456;       // Modifica el valor de i
      Console.WriteLine("Valor (tipo valor) = {0}", i);
      Console.WriteLine("Valor (tipo object)= {0}", o);
   }
}

El resultado de su ejecución es:

Valor (tipo valor) = 456
Valor (tipo object)= 123

Unboxing

Una vez que se ha hecho boxing sobre un dato y disponemos de un object no puede hacerse demasiado con él ya que no pueden emplearse métodos o propiedades del tipo original: el compilador no puede conocer el tipo original sobre el que se hizo boxing.

   string s1 = "Hola";
   object o  = s1;      // boxing
   ...
   if (o.Lenght > 0)  // ERROR

Una vez que se ha hecho boxing puede deshacerse la conversión (unboxing) haciendo casting explícito al tipo de dato inicial.

   string s2 = (string) o;   // unboxing

Afortunadamente es posible conocer el tipo, de manera que si la conversión no es posible se lanza una excepción. Pueden utilizarse los operadores is y as para determinar la corrección de la conversión:

   string s2;
   if (o is string) 
      s2 = (string) o;   // unboxing

o alternativamente:

   string s2 = o as string;  // conversion 
   if (s2 != null) // OK, la conversion funciono

Conclusiones

Ventajas del sistema unificado de tipos: las colecciones funcionan sobre cualquier tipo.

Hashtable t = new Hashtable();

t.Add(0, "zero");
t.Add(1, "one");
t.Add(2, "two");
string s = string.Format("Your total was {0} on {1}", total, date);

Desventajas del sistema unificado de tipos: Eficiencia.

La necesidad de utilizar boxing disminuirá cuando el CLR permita genéricos (algo similar a los templates en C++).

Cadenas de caracteres

Una cadena de caracteres no es más que una secuencia de caracteres Unicode. En C# se representan mediante objetos del tipo string, que no es más que un alias del tipo System.String incluido en la BCL.

El tipo string es un tipo referencia. Representa una serie de caracteres inmutable. Se dice que una instancia de String es inmutable porque no se puede modificar su valor una vez creada. Los métodos que aparentemente modifican una cadena devuelven en realidad una cadena nueva que contiene la modificación.

Recuerde la particularidad de este tipo sobre el operador de asignación: su funcionamiento es como si fuera un tipo valor. Este es, realmente, el funcionamiento obtenido con el método Copy(). Si no se desea obtener una copia (independiente) sino una copia en el sentido de un tipo valor emplee el método Clone().

Puede accederse a cada uno de los caracteres de la cadena mediante índice, como ocurre habitualmente en otros lenguajes, siendo la primera posición asociada al índice cero. Este acceso, no obstante, sólo está permitido para lectura.

   string s = "!!Hola, mundo!!";;
   for (int i=0; i < s.Length; i++) 
      Console.Write (s[i]);

Este ejemplo muestra todos los caracteres que componen la cadena, uno a uno. El ciclo realiza tantas iteraciones como número de caracteres forman la cadena (su longitud) que se consulta usando la propiedad Length.Por definición, un objeto String, incluida la cadena vacía (""), es mayor que una referencia nula y dos referencias nulas son iguales entre sí. El carácter null se define como el hexadecimal 0x00. Puede consultarse si una cadena es vacía empleando la propiedad estática (sólo lectura) Empty: el valor de este campo es la cadena de longitud cero o cadena vacía, "". Una cadena vacía no es igual que una cadena cuyo valor sea null.

Los procedimientos de comparación y de búsqueda distinguen mayúsculas de minúsculas de forma predeterminada. Pueden emplearse los métodos Compare() y Equals() para realizar referencias combinadas y comparaciones entre valores de instancias de Object y String. Los operadores relacionales == y != se implementan con el método Equals().

El método Concat() concatena una o más instancias de String o las representaciones de tipo String de los valores de una o más instancias de Object. El operador + está sobrecargado para realizar la concatenación. Por ejemplo, el siguiente código:

   string s1 = "Esto es una cadena... como en C++";
   string s2 = "Esto es una cadena... ";
   string s3 = "como en C++";
   string s4 = s2 + s3;
   string s5 = String.Concat(s2, s3);

   Console.WriteLine ("s1 = {0}", s1);
   Console.WriteLine ("s4 = {0}", s4);
   Console.WriteLine ("s5 = {0}", s5);

   if ((s1 == s4) && (s1.Equals(s5))) 
      Console.WriteLine ("s1 == s4 == s5");

produce este resultado:

s1 = Esto es una cadena... como en C++
s4 = Esto es una cadena... como en C++
s5 = Esto es una cadena... como en C++
s1 == s4 == s5

Un método particularmente útil es Split(), que devuelve un vector de cadenas resultante de “partir” la cadena sobre la que se aplica en palabras:

   string linea;
   string [] palabras; 
   ...
   palabras = linea.Split (null); // null indica dividir por espacios

En definitiva, la clase String incluye numerosos métodos, que pueden emplease para:

  • Realizar búsquedas en cadenas de caracteres: IndexOf(), LastIndexOf(), StartsWith(), EndsWith()
  • Eliminar e insertar espacios en blanco: Trim(), PadLeft(), PadRight()
  • Manipular subcadenas: Insert(), Remove(), Replace(), Substring(), Join()
  • Modificar cadenas de caracteres: ToLower(), ToUpper(), Format() (al estilo del printf de C, pero seguro).

Vectores y matrices

Un vector (matriz) en C# es radicalmente diferente a un vector (matriz) en C++: más que una colección de variables que comparten un nombre y accesibles por índice, en C# se trata de una instancia de la clase System.Array, y en consecuencia se trata de una colección que se almacena en el heap y que está bajo el control del gestor de memoria.

Todas las tablas que definamos, sea cual sea el tipo de elementos que contengan, son objetos que derivan de System.Array. Ese espacio de nombres proporciona métodos para la creación, manipulación, búsqueda y ordenación de matrices, por lo tanto, sirve como clase base para todas las matrices de la CLR (Common Language Runtime).

En C# las tablas pueden ser multidimensionales, se accede a los elementos por índice, siendo el índice inicial de cada dimensión 0.

Siempre se comprueba que se esté accediendo dentro de los límites. Si se intenta acceder a un elemento de un vector (matriz) especificando un índice fuera del rango, se detecta en tiempo de ejecución y se lanza una excepción IndexOutOfBoundsException.

La sintaxis es ligeramente distinta a la del C++ porque las tablas son objetos de tipo referencia:

   double [] array;        // Declara un a referencia 
                           // (no se instancia ningún objeto) 
   array = new double[10]; // Instancia un objeto de la clase 
                           // System.Array y le asigna 10 casillas. 

que combinadas resulta en (lo habitual):

   double [] array = new double[10]; 
  • El tamaño del vector se determina cuando se instancia, no es parte de la declaración.
       string [] texto;   // OK
       string [10] texto; // Error
    
  • La declaración emplea los paréntesis vacíos [ ] entre el tipo y el nombre para determinar el número de dimensiones (rango).
       string []    Mat1D; // 1 dimension
       string [,]   Mat2D; // 2 dimensiones
       string [,,]  Mat3D; // 3 dimensiones
       string [,,,] Mat4D; // 4 dimensiones
       ......
    
  • En C# el rango es parte del tipo (es obligatorio en la declaración). El número de elementos no lo es (está asociado a la instancia concreta).

Otros ejemplos de declaración de vectores:

    string[] a = new string[10]; // "a" es un vector de 10 cadenas
    int[] primes = new int[9];   // "primes" es un vector de 9 enteros 

Un vector puede inicializarse a la misma vez que se declara. Las tres definiciones siguientes son equivalentes:

    int[] prime1 = new int[10] {1,2,3,5,7,11,13,17,19,23};
    int[] prime2 = new int[] {1,2,3,5,7,11,13,17,19,23};
    int[] prime3 = {1,2,3,5,7,11,13,17,19,23};

Los vectores pueden dimensionarse dinámicamente (en tiempo de ejecución). Por ejemplo, el siguiente código:

    Console.Write ("Num. casillas: "); 
    string strTam = Console.ReadLine();
    int tam = Convert.ToInt32(strTam); 

    int[] VecDin = new int[tam];
    for (int i=0; i<tam; i++) VecDin[i]=i;

    Console.Write ("Contenido de VecDin = "); 
    foreach (int i in VecDin) 
       Console.Write(i + ", ");

    Console.ReadLine();

produce este resultado:

Num. casillas: 6
Contenido de VecDin = 0, 1, 2, 3, 4, 5, 

Esta facilidad es una gran ventaja, aunque una vez que el constructor actúa y se crea una instancia de la clase System.Array no es posible redimensionarlo. Si se desea una estructura de datos con esta funcionalidad debe emplearse una estructura colección disponible en System.Collections (por elemplo, System.Collections.ArrayList).

En el siguiente ejemplo observe cómo el vector palabras se declara como un vector de string. Se instancia y se inicia después de la ejecución del método Split(), que devuelve un vector de string.

   string frase = "Esto es una prueba de particion";
   string [] palabras;  // vector de cadenas (no tiene tamaño asignado)

   palabras = frase.Split (null);

   Console.WriteLine ("Frase = {0}", frase);
   Console.WriteLine ("Hay = {0} palabras", palabras.Length);
   
   for (int i=0; i < palabras.Length; i++) 
      Console.WriteLine ("   Palabra {0} = {1}", i, palabras[i]);

El resultado es:

    
Frase = Esto es una prueba de particion
Hay = 6 palabras
   Palabra 0 = Esto
   Palabra 1 = es
   Palabra 2 = una
   Palabra 3 = prueba
   Palabra 4 = de
   Palabra 5 = particion

Matrices

Las diferencias son importantes respecto a C++ ya que C# permite tanto matrices rectangulares (todas las filas tienen el mismo número de columnas) como matrices dentadas o a jirones.

   int [,] array2D;         // Declara un a referencia 
                            // (no se instancia ningún objeto) 
   array2D = new int [2,3]; // Instancia un objeto de la clase 
                            // System.Array y le asigna 6 casillas 
                            // (2 filas con 3 columnas cada una) 

que combinadas (y con inicialización) podría quedar:

   int [,] array2D = new int [2,3] {{1,0,4}, {3,2,5}}; 

El número de dimensiones puede ser, lógicamente, mayor que dos:

   int [,,] array3D = new int [2,3,2]; 

El acceso se realiza con el operador habitual [ ], aunque el recorrido se simplica y clarifica en C# con el ciclo foreach:

   for (int i= 0; i< vector1.Length; i++)
       vector1[i] = vector2[i];
   ...
   foreach (float valor in vector2)
       Console.Wtite (valor);

Una matriz dentada no es más que una tabla cuyos elementos son a su vez tablas, pudiéndose así anidar cualquier número de tablas. Cada tabla puede tener un número propio de casillas. En el siguiente ejemplo se pide el número de filas, y para cada fila, el número de casillas de ésa.

    Console.WriteLine ("Introduzca las dimensiones: "); 

    // Peticion de numero de filas (num. vectores) 
    Console.Write ("Num. Filas: "); 
    string strFils = Console.ReadLine();
    int Fils = Convert.ToInt32(strFils); 

    // Declaracion de la tabla dentada: el numero de 
    //      casillas de cada fila es deconocido. 
    int[][] TablaDentada = new int[Fils][]; 

    // Peticion del numero de columnas de cada vector 
    for (int f=0; f<Fils; f++) 
    {
       Console.Write ("Num. Cols. de fila {0}: ", f); 
       string strCols = Console.ReadLine();
       int Cols = Convert.ToInt32(strCols); 

       // Peticion de memoria para cada fila
       TablaDentada[f] = new int[Cols];
    }

    // Rellenar todas las casillas de la tabla dentada 
    for (int f=0; f<TablaDentada.Length; f++) 
       for (int c=0; c<TablaDentada[f].Length; c++) 
          TablaDentada[f][c]=((f+1)*10)+(c+1); 

    // Mostrar resultado
    Console.WriteLine ("Contenido de la matriz: "); 
    for (int f=0; f<TablaDentada.Length; f++) 
    {
       for (int c=0; c<TablaDentada[f].Length; c++) 
          Console.Write (TablaDentada[f][c] + "  "); 
       Console.WriteLine();
    }

    Console.ReadLine();

El resultado es:

Introduzca las dimensiones:
Num. Filas: 4
Num. Cols. de fila 0: 2
Num. Cols. de fila 1: 5
Num. Cols. de fila 2: 3
Num. Cols. de fila 3: 7
Contenido de la matriz:
11  12
21  22  23  24  25
31  32  33
41  42  43  44  45  46  47

Estructuras

Una estructura (struct) se emplea para definir nuevos tipos de datos. Los nuevos tipos así definidos son tipos valor (se almacenan en la pila).

La sintaxis para la definición de estructuras es similar a la empleada para las clases (se emplea la palabra reservada struct en lugar de class). No obstante,

  • La herencia y aspectos relacionados (p.e. métodos virtuales, métodos abstractos) no se admiten en los struct.
  • No se puede especificar (explícitamente) un constructor sin parámetros. Los datos struct tienen un constructor sin parámetros predefinido y no puede reemplazarse, sólo se permiten constructores con parámetros. En este sentido son diferentes a los struct en C++.
  • En el caso de especificar algún constructor con parámetros deberíamos asegurarnos de iniciar todos los campos.

En el siguiente ejemplo se proporciona un único constructor con parámetros:

   public struct Point  
   {
      public int x, y;

      public Point(int x, int y) 
      {
         this.x = x; 
         this.y = y;
      }
      public string Valor() 
      {
         return ("[" + this.x + "," + this.y + "]");   
      }
   }

Sobre esta declaración de tipo Point, observe estas declaraciones de datos Point:

 
    Point p  = new Point(2,5); 
    Point p2 = new Point();   

Ambas crean una instancia de la clase Point en la pila y asigna los valores oportunos a los campos del struct con el constructor:

  • En el primer caso actúa el constructor suministrado en la implementación del tipo.
  • En el segundo actúa el constructor por defecto, que inicia los campos numéricos a cero, y los de tipo referencia a null.

Puede comprobarse fácilmente:

   Console.WriteLine ("p  = " + p.Valor());     
   Console.WriteLine ("p2 = " + p2.Valor());

produce como resultado:

p  = [2,5]
p2 = [0,0]

En cambio, la siguiente declaración:

    Point p3; 

crea una instancia de la clase Point en la pila sin iniciar (cuidado: no inicia p3 a null, no es un tipo referencia). Por lo tanto, la instrucción:

   Console.WriteLine ("p3 = " + p3.Valor());

produce un error de compilación, al intentar usar una variable no asignada.Observe las siguientes operaciones con struct. Su comportamiento es previsible:

 
   p.x += 100;
   int px = p.x;       // p.x==102
   p3.x = px;          // p3.x==102

   p2 = p;             // p2.x==102, p2.y==5
   p2.x += 100;        // p2.x==202, p2.y==5
   p3.y = p.y + p2.y;  // p3.y==10

   Console.WriteLine ("p  = " + p.Valor());     
   Console.WriteLine ("p2 = " + p2.Valor());
   Console.WriteLine ("p3 = " + p3.Valor());

el resultado es:

 
p  = [102,5]
p2 = [202,5]
p3 = [102,10]

Enumeraciones

Una enumeración o tipo enumerado es un tipo especial de estructura en la que los literales de los valores que pueden tomar sus objetos se indican explícitamente al definirla.

La sintaxis es muy parecida a la empleada en C++:

   enum State { Off, On };

aunque el punto y coma final es opcional ya que una enumeración es una definición de un struct y ésta no requiere el punto y coma:

   enum State { Off, On }

Al igual que en C++ y en C, C# numera a los elementos de la enumeración con valores enteros sucesivos, asignando el valor 0 al primero de la enumeración, a menos que se especifique explícitamente otra asignación:

   enum Tamanio {
      Pequeño = 1, 
      Mediano = 3,
      Grande  = 5
      Inmenso
   }

En el ejemplo, el valor asociado a Inmenso es 6.Para acceder a los elementos de una enumeración debe cualificarse completamente:

   Tamanio Ancho = Tamanio.Grande; 

lo que refleja que cada enumeración es, en última instancia, un struct.Si no se declara ningún tipo subyacente de forma explícita, se utiliza Int32. En el siguiente ejemplo se especifica el tipo base byte:

   enum Color: byte {
      Red   = 1,
      Green = 2,
      Blue  = 4,
      Black = 0,
      White = Red | Green | Blue
   }

La clase Enum (System.Enum) proporciona la clase base para las enumeraciones. Proporciona métodos que permiten comparar instancias de esta clase, convertir el valor de una instancia en su representación de cadena, convertir la representación de cadena de un número en una instancia de esta clase y crear una instancia de una enumeración y valor especificados. Por ejemplo:

   Color c1 = Color.Black;
      
   Console.WriteLine((int) c1);       // 0
   Console.WriteLine(c1);             // Black
   Console.WriteLine(c1.ToString());  // Black

Pueden convertirse explícitamente en su valor entero (como en C). Admiten determinados operadores aritméticos (+,-,++,–) y lógicos a nivel de bits (&,|,^,~). ejemplo:

   Color c2 = Color.White;
   
   Console.WriteLine((int) c2);      // 7
   Console.WriteLine(c2.ToString()); // White

Otro ejemplo más complejo:

enum ModArchivo
{ 
   Lectura = 1,
   Escritura = 2,
   Oculto = 4,
   Sistema = 8
} 
   ...
   ModArchivo st = ModArchivo.Lectura | ModArchivo.Escritura; 
   ...
   Console.WriteLine (st.ToString("F"));                         // Lectura, Escritura
   Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "G")); // 3
   Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "X")); // 00000003
   Console.WriteLine (Enum.Format(typeof(ModArchivo), st, "D")); // 3





Leer-->  Conexiones .Net Con Access 2007 o 2010




Siguenos en Twitter

#Meteocanción🎵🎶 15 de febrero: Fire And Rain (James Taylor) https://t.co/tLZkugcv2i #meteocanción

The latest The la web de wayner Daily! https://t.co/z8TbL4kD2m Thanks to @Vibra1049 @YucatanEnCorto #meteocanción #fdlf

Las extensiones de pelo de la nariz son la nueva moda en Instagram porque 2017 es así https://t.co/XdiNJ3CKcx

Load More...

Gana dinero por compartir tus enlaces!
A %d blogueros les gusta esto: