Examen del sistema de tipos de .NET
C# es un lenguaje fuertemente tipado. Cada variable y constante tiene un tipo, al igual que todas las expresiones que se evalúan como un valor. La biblioteca de clases de .NET define tipos numéricos integrados y tipos complejos que representan una amplia variedad de construcciones. Estas construcciones incluyen el sistema de archivos, las conexiones de red, las colecciones y las matrices de objetos y fechas. Un programa típico de C# usa tipos de la biblioteca de clases y tipos definidos por el usuario que modela los conceptos específicos del dominio de problemas del programa.
Tipos integrados
C# proporciona un conjunto estándar de tipos integrados. Estos tipos estándar representan enteros, valores de punto flotante, expresiones booleanas, caracteres de texto, valores decimales y otros tipos de datos. También hay tipos de cadenas y objetos integrados. Estos tipos están disponibles para su uso en cualquier programa de C#.
Tipos personalizados
Use las construcciones struct, class, interface, enum, y record para crear sus propios tipos personalizados. La propia biblioteca de clases de .NET es una colección de tipos personalizados que puede usar en sus propias aplicaciones. De forma predeterminada, los tipos más usados de la biblioteca de clases están disponibles en cualquier programa de C#. Otros solo están disponibles cuando se agrega explícitamente una referencia de proyecto al ensamblado que las define. Después de que el compilador tenga una referencia al ensamblado, puede declarar variables (y constantes) de los tipos declarados en ese ensamblado en el código fuente.
Sistema de tipos comunes
Es importante comprender dos puntos fundamentales sobre el sistema de tipos en .NET:
Admite el principio de herencia. Los tipos pueden derivar de otros tipos, denominados tipos base. El tipo derivado hereda (con algunas restricciones) los métodos, las propiedades y otros miembros del tipo base. El tipo base puede derivar a su vez de algún otro tipo, en cuyo 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), derivan en última instancia de un único tipo base, que es
System.Object(palabra clave de C#:object). Esta jerarquía de tipos unificada se denomina Common Type System (CTS).Cada tipo de CTS se define como un tipo de valor o un tipo de referencia. Estos tipos incluyen todos los tipos personalizados en la biblioteca de clases de .NET y también sus propios tipos definidos por el usuario. Los tipos que defina mediante la palabra clave struct son tipos de valor; todos los tipos numéricos integrados son estructuras. Los tipos que defina mediante la palabra clave class o record son tipos de referencia. Los tipos de referencia y los tipos de valor tienen reglas en tiempo de compilación diferentes y un comportamiento en tiempo de ejecución diferente.
En la ilustración siguiente se muestra la relación entre los tipos de valor y los tipos de referencia en el CTS.

Las clases y las estructuras son dos de las construcciones básicas del sistema de tipos común en .NET. Cada es básicamente una estructura de datos que encapsula un conjunto de datos y comportamientos que pertenecen a ellos como una unidad lógica. Los datos y los comportamientos son los miembros de class, struct, o record. Los miembros de una clase incluyen propiedades (datos), métodos (comportamientos), campos (variables declaradas dentro de la clase) y muchos otros.
Una declaración class, struct, o record es como un plano técnico que se usa para crear instancias o objetos en tiempo de ejecución. Si define una clase denominada Person, Person es el nombre del tipo. Si declara e inicializa una variable p de tipo Person, se dice que p es un objeto o instancia de Person. Se pueden crear varias instancias del mismo tipo de Person y cada instancia puede tener valores diferentes en sus propiedades y campos.
Una clase es un tipo de referencia. Cuando se crea un objeto del tipo, la variable a la que se asigna el objeto contiene solo una referencia a esa memoria. Cuando la referencia de objeto se asigna a una nueva variable, la nueva variable hace referencia al objeto original. Los cambios realizados a través de una variable se reflejan en la otra variable porque ambos hacen referencia a los mismos datos.
A struct es un tipo de valor. Cuando se crea un struct, la variable a la que se asigna el struct contiene los datos reales del struct. Cuando el struct se asigna a una nueva variable, se copia el valor de datos. Por lo tanto, la nueva variable y la variable original contienen dos copias independientes de los mismos datos. Los cambios realizados en una copia no afectan a la otra copia.
Los tipos de registro pueden ser tipos de referencia (record class) o tipos de valor (record struct). Los tipos de registro contienen métodos que admiten la igualdad de valores.
En general, las clases se usan para modelar un comportamiento más complejo. Las clases suelen almacenar datos que están diseñados para modificarse después de crear un objeto de clase. Las estructuras son más adecuadas para estructuras de datos pequeñas. Las estructuras suelen almacenar datos que no están diseñados para modificarse después de crear. struct Los tipos de registro son estructuras de datos con otros miembros sintetizados del compilador. Los registros suelen almacenar datos que no están diseñados para modificarse después de crear el objeto.
Tipos de valor
Los tipos de valor derivan de System.ValueType, que deriva de System.Object. Los tipos que derivan de System.ValueType tienen un comportamiento especial en Common Language Runtime. Las variables de tipo de valor contienen directamente sus valores. La memoria de un struct objeto se asigna en línea en cualquier contexto que se declare la variable. No hay ninguna sobrecarga de asignación de montón o recolección de elementos no utilizados independientes para las variables de tipo valor. Puede declarar tipos de estructura de registro que son tipos de valor e incluir los miembros sintetizados para los registros.
Hay dos categorías de tipos de valor: estructura y enumeración.
Los tipos numéricos integrados son estructuras y tienen campos y métodos a los que puede acceder:
// constant field on type byte.
byte b = byte.MaxValue;
Pero declara y asigna valores a ellos como si fueran tipos no agregados simples:
byte num = 0xA;
int i = 5;
char c = 'Z';
Los tipos de valor están sellados. No se puede derivar un tipo de ningún tipo de valor, por ejemplo System.Int32. No se puede definir para struct heredar de ningún usuario definido class por el usuario o struct porque un struct solo puede heredar de System.ValueType.
Use la palabra clave struct para crear sus propios tipos de valor personalizados. Normalmente, una estructura se usa como contenedor para 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;
}
}
La otra categoría de tipos de valor es enum. Un enum define un conjunto de constantes integrales con nombre. Por ejemplo, la enumeración System.IO.FileMode de la biblioteca de clases de .NET contiene un conjunto de enteros constantes con nombre que especifican cómo se debe abrir un archivo. La sintaxis de un enum se muestra en el ejemplo siguiente:
public enum FileMode
{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}
La constante System.IO.FileMode.Create tiene un valor de 2. Sin embargo, el nombre es mucho más significativo para los seres humanos que leen el código fuente y, por ese motivo, es mejor usar enumeraciones en lugar de números literales constantes.
Todos los enums heredan de System.Enum, que hereda de System.ValueType. Todas las reglas que se aplican a structs también se aplican a enums.
Tipos de referencia
Los classtipos , record, delegate, arrayy interface son tipos de referencia.
Cuando se declara una variable de un tipo de referencia, contiene el valor null hasta que se le asigna con una instancia de ese tipo o se crea una mediante el new operador.
En el ejemplo siguiente se muestra cómo declarar variables de tipo de referencia mediante matrices:
// Declaring an array variable
int[] numbers;
// Initializing the array with a size of 5
numbers = new int[5];
// Alternatively, declaring and initializing an array in one line
int[] numbers2 = new int[] { 1, 2, 3, 4, 5 };
// Assigning a reference to another variable
int[] numbers3 = numbers2;
El new operador crea una instancia del tipo y devuelve una referencia a esa instancia. La referencia es la dirección de memoria del objeto y esa referencia se almacena en la variable. Al asignar una variable de tipo de referencia a otra variable, va a copiar la referencia, no el propio objeto. Ambas variables hacen referencia al mismo objeto en memoria.
Nota:
Además de ser tipos de referencia, las matrices son colecciones. Las colecciones se pueden inicializar mediante expresiones de colección, lo que elimina el requisito de incluir la new palabra clave al declarar e inicializar una matriz en una línea. Por ejemplo: int[] numbers = [ 1, 2, 3, 4, 5 ];.
Puede crear una instancia de una clase con la misma sintaxis que se usa para crear instancias de los tipos integrados. En el ejemplo siguiente se muestra cómo crear una instancia de una clase:
MyClass myClass = new MyClass();
MyClass myClass2 = myClass;
El new operador crea una instancia de la clase y devuelve una referencia a esa instancia. La referencia es la dirección de memoria del objeto y esa referencia se almacena en la variable. Al asignar una variable de tipo de referencia a otra variable, va a copiar la referencia, no el propio objeto. Ambas variables hacen referencia al mismo objeto en memoria.