Tipos (Guía de programación de C#)

Actualización: Julio de 2008

C# es un lenguaje con establecimiento inflexible de tipos. Todas las variables y constantes tienen un tipo, al igual que toda expresión que da como resultado un valor. Cada firma de método especifica un tipo para cada parámetro de entrada y para el valor devuelto. La biblioteca de clases .NET Framework define un conjunto de tipos numéricos integrados y tipos más complejos que representan una amplia variedad de construcciones lógicas, como el sistema de archivos, conexiones de red, colecciones y matrices de objetos y fechas. Un programa típico de C# usa los tipos de la biblioteca de clases, así como tipos definidos por el usuario que modelan los conceptos específicos del dominio problemático del programa.

La información almacenada en un tipo puede incluir lo siguiente:

  • El espacio de almacenamiento que requiere una variable del tipo.

  • Los valores máximo y mínimo que puede representar.

  • Los miembros (métodos, campos, eventos, etc.) que contiene.

  • El tipo base del que hereda.

  • La ubicación donde se asignará la memoria para las variables en tiempo de ejecución.

  • Los tipos de operaciones permitidos.

El compilador utiliza información de tipos para asegurarse de que todas las operaciones que se realizan en el código cumplen la seguridad de tipos. Por ejemplo, si declara una variable de tipo int, el compilador permite utilizar la variable en operaciones de suma y resta. Si intenta realizar esas mismas operaciones con una variable de tipo bool, el compilador genera un error, como se muestra en el ejemplo siguiente:

int a = 5;             
int b = a + 2; //OK

bool test = true;
 * 
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;


Nota:

Los desarrolladores de C y C++ deben tener en cuenta que en C# bool no se puede convertir en int.

El compilador incrusta la información de tipos en el archivo ejecutable en forma de metadatos. Common Language Runtime (CLR) utiliza dichos metadatos en tiempo de ejecución para reforzar la garantía de la seguridad de tipos cuando asigna y reclama memoria.

Especificar los tipos en declaraciones de variables

Al declarar una variable o una constante en un programa, debe especificar su tipo o utilizar la palabra clave var para permitir que el compilador infiera el tipo. En el ejemplo siguiente se muestran algunas declaraciones de variables que utilizan tipos numéricos integrados y tipos complejos definidos por el usuario:

// Declaration only:
float temperature;
string name;
MyClass myClass;

// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
            where item <= limit
            select item;


Los tipos de los parámetros de método y de los valores devueltos se especifican en la firma del método. La firma siguiente muestra un método que requiere int como argumento de entrada y devuelve una cadena:

public string GetName(int ID)
{
    if (ID < names.Length)
        return names[ID];
    else
        return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };


Una vez declarada una variable, ésta no se puede volver a declarar con un nuevo tipo y tampoco se le puede asignar un valor que no sea compatible con su tipo declarado. Por ejemplo, no puede declarar int y, a continuación, asignarle un valor booleano de true. Sin embargo, los valores pueden convertirse en otros tipos, por ejemplo, cuando se asignan a variables nuevas o se pasan como argumentos de método. Una conversión de tipos que no ocasiona una pérdida de datos la realiza automáticamente el compilador. Una conversión que puede causar una pérdida de datos requiere una conversión de tipos en el código fuente.

Para obtener más información, vea Conversiones de tipos (Guía de programación de C#).

C# proporciona un conjunto estándar de tipos numéricos integrados para representar enteros, valores de punto flotante, expresiones Boolean, caracteres de texto, valores decimales y otros tipos de datos. También hay tipos object y string integrados. Dichos tipos están disponibles para que los utilice en cualquier programa de C#. Para obtener más información sobre los tipos integrados, vea Tablas de referencia de tipos (Referencia de C#).

Las construcciones struct, class, interface y enum se utilizan para crear sus propios tipos personalizados. La biblioteca de clases de .NET Framework en sí es una colección de tipos personalizados proporcionada por Microsoft que puede utilizar en sus propias aplicaciones. De forma predeterminada, los tipos usados con mayor frecuencia en la biblioteca de clases están disponibles en cualquier programa de C#. Otros solo están disponibles cuando se agrega explícitamente una referencia del proyecto al ensamblado en el que se definen. Una vez que el compilador incluye una referencia al ensamblado, puede declarar variables (y constantes) de los tipos declarados en dicho ensamblado en el código fuente. Para obtener más información, vea Biblioteca de clases de .NET Framework.

Es importante entender dos puntos fundamentales sobre el sistema de tipos en .NET Framework:

  • Éste admite el principio de herencia. Los tipos pueden derivarse de otros tipos denominados tipos base. El tipo derivado hereda (con algunas restricciones) los métodos, las propiedades y otros miembros del tipo base. A su vez, el tipo base puede derivarse de algún otro tipo; en este caso, el tipo derivado hereda los miembros de ambos tipos base en su jerarquía de herencia. Todos los tipos, incluidos los tipos numéricos integrados como System.Int32 (palabra clave de C#: int), se derivan en última instancia de un único tipo base, que es System.Object (palabra clave de C#: object). Esta jerarquía de tipos unificados se denomina Sistema de tipos comunes (CTS). Para obtener más información sobre la herencia en C#, vea Herencia (Guía de programación de C#).

  • Cada uno de los tipos de CTS se define como un tipo de valor o un tipo de referencia. Esto incluye todos los tipos personalizados de la biblioteca de clases .NET Framework y también los tipos propios definidos por el usuario. Los tipos que define mediante la palabra clave struct son tipos de valor; todos los tipos numéricos integrados son structs. Los tipos que define mediante la palabra clave class son tipos de referencia. Los tipos de referencia y los tipos de valor tienen distintas reglas de tiempo de compilación y un comportamiento diferente en tiempo de ejecución.

En la ilustración siguiente se muestra la relación entre los tipos de valor y los tipos de referencia en CTS.

Tipos de valor y tipos de referencia en CTS

Tipos de valor y tipos de referencia
Nota:

Puede ver que todos los tipos utilizados con más frecuencia se organizan en el espacio de nombres System. Sin embargo, el espacio de nombres en el que se incluye un tipo no tiene ninguna relación con el hecho de que sea un tipo de valor o un tipo de referencia.

Tipos de valor

Los tipos de valor se derivan de System.ValueType, que a su vez se deriva de System.Object. Los tipos que se derivan de System.ValueType tienen un comportamiento especial en CLR. Las variables de tipo de valor contienen directamente sus valores, lo que significa que la memoria se asigna insertándola en cualquier contexto en el que se declare la variable. En las variables de tipo de valor no se produce una asignación de memoria independiente en el montón ni una sobrecarga de recolección de elementos no utilizados.

Hay dos categorías de tipos de valor: struct y enum.

Los tipos numéricos integrados son estructuras con propiedades y métodos a los que puede tener acceso:

// Static method on type Byte.
byte b = Byte.MaxValue();

Sin embargo, la declaración y asignación de valores a dichos tipos se realizan como si fueran tipos simples no agregados:

byte num = 0xA;
int i = 5;
char c = 'Z';

Los tipos de valor son sealed, lo que significa, por ejemplo, que no puede derivar un tipo de System.Int32 y que no puede definir una estructura para que herede de cualquier clase o estructura definida por el usuario, ya que una estructura solo puede heredar de System.ValueType. Sin embargo, una estructura puede implementar una o varias interfaces. Puede convertir un tipo de estructura en un tipo de interfaz; esto hace que una operación de conversión boxing encapsule la estructura en un objeto de tipo de referencia del montón administrado. Las operaciones de conversión boxing tienen lugar al pasar un tipo de valor a un método que toma un elemento System.Object como parámetro de entrada. Para obtener más información, vea Conversión boxing y unboxing (Guía de programación de C#).

La palabra clave struct se usa para crear sus propios tipos de valores personalizados. Normalmente, una estructura se utiliza como contenedor de un pequeño conjunto de variables relacionadas, como se muestra en el ejemplo siguiente:

public struct CoOrds
{
    public int x, y;

    public CoOrds(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}


Para obtener más información sobre las estructuras, vea Estructuras (Guía de programación de C#). Para obtener más información sobre los tipos de valor en .NET Framework, vea Tipos de valor en el sistema de tipos común.

La otra categoría de tipos de valor es enum. Una enumeración define un conjunto de constantes integrales con nombre. Por ejemplo, la enumeración System.IO.FileMode de la biblioteca de clases .NET Framework contiene un conjunto de enteros constantes con nombre que especifican cómo debe abrirse un archivo. Se define como se muestra en el ejemplo siguiente:

public enum FileMode
{
    CreateNew = 1,
    Create = 2,
    Open = 3,
    OpenOrCreate = 4,
    Truncate = 5,
    Append = 6,
}


La constante Create tiene el valor 2. Sin embargo, el nombre es mucho más significativo para los humanos que leen el código fuente y, por esa razón, es mejor utilizar enumeraciones en lugar de números de literal constante.

Todas las enumeraciones heredan de System.Enum, que a su vez hereda de System.ValueType. Todas las reglas aplicables a las estructuras también se aplican a las enumeraciones. Para obtener más información sobre las enumeraciones, vea Tipos de enumeración (Guía de programación de C#).

Tipos de referencia

Un tipo que se define como clase, delegado, matriz o interfaz es un tipo de referencia. Al declarar una variable de un tipo de referencia en tiempo de ejecución, la variable contiene el valor null hasta que se crea explícitamente una instancia del objeto mediante el operador new o se le asigna un objeto creado en otro lugar mediante new, as shown in the following example:.

MyClass mc = new MyClass();
MyClass mc2 = mc;

Una interfaz debe inicializarse junto con un objeto de clase que la implementa. Si MyClass implementa IMyInterface, cree una instancia de IMyInterface como se muestra en el ejemplo siguiente:

IMyInterface iface = new MyClass();

Cuando se crea el objeto, se asigna la memoria en el montón administrado y la variable solo contiene una referencia a la ubicación del objeto. Los tipos del montón administrado producen sobrecarga cuando se asignan y cuando los reclama la funcionalidad de administración de memoria automática de CLR, conocida como recolección de elementos no utilizados. Sin embargo, la recolección de elementos no utilizados también está muy optimizada y no crea problemas de rendimiento en la mayoría de los casos. Para obtener más información acerca de la recolección de elementos no utilizados, vea Administración de memoria automática.

Todas las matrices son tipos de referencia, incluso si sus elementos son tipos de valor. Las matrices se derivan implícitamente de la clase System.Array, pero se declaran y utilizan con la sintaxis simplificada proporcionada por C#, como se muestra en el ejemplo siguiente:

// Declare and initialize an array of integers.
int[] nums = { 1, 2, 3, 4, 5 };

// Access an instance property of System.Array.
int len = nums.Length;


Los tipos de referencia admiten la herencia. Al crear una clase, se puede heredar de cualquier otra interfaz o clase que no esté definida como sealed; además, otras clases pueden heredar de la clase que ha creado e invalidar sus métodos virtuales. Para obtener más información sobre cómo crear sus propias clases, vea Clases y estructuras (Guía de programación de C#). Para obtener más información acerca de la herencia y los métodos virtuales, vea Herencia (Guía de programación de C#).

En C#, los valores literales reciben un tipo del compilador. Para especificar el tipo de un literal numérico, anexe una letra al final del número. Por ejemplo, para especificar que el valor 4.56 debe tratarse como tipo flotante, anexe una "f" o "F" después del número: 4.56f. Si no se anexa ninguna letra, el compilador inferirá un tipo para el literal. Para obtener más información sobre los tipos que pueden especificarse con sufijos de letras, vea las páginas de referencia de los tipos individuales en Tipos de valores (Referencia de C#).

Puesto que los literales tienen tipos y todos los tipos se derivan en última instancia de System.Object, puede escribir y compilar código como el que se muestra a continuación:

string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);

Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);


Un tipo se puede declarar con uno o varios parámetros de tipo que actúan como marcador de posición del tipo real (el tipo concreto) que proporcionará el código cliente al crear una instancia del tipo. Estos tipos se denominan tipos genéricos. Por ejemplo, el tipo de .NET Framework System.Collections.Generic.List<T> tiene un parámetro de tipo al que, por convención, se le asigna el nombre T. Cuando se crea una instancia del tipo, se especifica el tipo de los objetos que incluirá la lista, por ejemplo, cadena:

List<string> strings = new List<string>();

El uso de un parámetro de tipo hace que sea posible reutilizar la misma clase para incluir cualquier tipo de elemento, sin tener que convertir cada elemento en object. Las clases de colección genéricas se denominan colecciones con establecimiento inflexible de tipos porque el compilador conoce el tipo específico de los elementos de la colección y puede producir un error en tiempo de compilación si, por ejemplo, se intenta agregar un entero al objeto strings en el ejemplo anterior. Para obtener más información, vea Genéricos (Guía de programación de C#).

Como ya se ha mencionado anteriormente, puede indicar el tipo implícito de una variable local (pero no de los miembros de clase) mediante la palabra clave var. La variable sigue recibiendo un tipo en tiempo de compilación, pero el tipo lo proporciona el compilador. Para obtener más información, vea Variables locales con asignación implícita de tipos (Guía de programación de C#).

En algunos casos, es conveniente crear un tipo con nombre para conjuntos sencillos de valores relacionados que no están destinados a almacenarse o pasarse fuera de los límites del método. Para este fin, puede crear tipos anónimos. Para obtener más información, vea Tipos anónimos (Guía de programación de C#).

Los tipos de valor normales no pueden tener el valor null. Sin embargo, puede crear tipos de valor que acepten valores NULL si anexa ? después del tipo. Por ejemplo, int? es un tipo int que también puede tener el valor null. En CTS, los tipos que aceptan valores NULL son instancias del tipo de estructura genérica System.Nullable<T>. Dichos tipos resultan especialmente útiles al pasar datos desde y hacia bases de datos en las que los valores numéricos pueden ser null. Para obtener más información, vea Tipos que aceptan valores NULL (Guía de programación de C#).

Para obtener más información sobre los tipos, vea las secciones siguientes de Especificación del lenguaje C#:

  • 1.3 Tipos y variables

  • 3.8 Espacios de nombres y nombres de tipos

  • 4.1 Tipos de valor

  • 4.2 Tipos de referencia

  • 4.3 Conversiones boxing y unboxing

Fecha

Historial

Motivo

Julio de 2008

Se ha agregado información preliminar e información sobre declaraciones de tipos, Common Type System, tipos de valor y de referencia, literales y tipos genéricos.

Mejora de la información.

Adiciones de comunidad

Mostrar: