Tipos de referencia integrados (referencia de C#)

C# tiene muchos tipos de referencia integrados. Tienen palabras clave u operadores que son sinónimos para un tipo en la biblioteca de .NET.

El tipo de objeto

El tipo object es un alias de System.Object en .NET. En el sistema de tipos unificado de C#, todos los tipos, los predefinidos y los definidos por el usuario, los tipos de referencia y los tipos de valores, heredan directa o indirectamente de System.Object. Puede asignar valores de cualquier tipo a las variables de tipo object. Cualquier variable object puede asignarse a su valor predeterminado con el literal null. Cuando una variable de un tipo de valor se convierte en objeto, se dice que se aplica la conversión boxing. Cuando una variable de tipo object se convierte en un tipo de valor, se dice que se aplica la conversión unboxing. Para obtener más información, vea Conversión boxing y unboxing.

Tipo string

El tipo string representa una secuencia de cero o más caracteres Unicode. string es un alias de System.String en .NET.

Aunque string es un tipo de referencia, se definen los operadores de igualdad == y != para comparar los valores de los objetos string, no las referencias. La igualdad basada en valores hace que las pruebas de igualdad de cadenas sean más intuitivas. Por ejemplo:

string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));

En el ejemplo anterior se muestra "True" y luego "False" porque el contenido de las cadenas es equivalente, pero a y b no hacen referencia a la misma instancia de cadena.

El operador + concatena cadenas:

string a = "good " + "morning";

El código anterior crea un objeto de cadena que contiene "good morning".

Las cadenas son inmutables: el contenido de un objeto de cadena no se puede modificar una vez creado el objeto. Por ejemplo, al escribir este código, el compilador crea en realidad otro objeto de cadena para almacenar la nueva secuencia de caracteres, y este nuevo objeto se asigna a b. La memoria que se había asignado para b (cuando contiene la cadena "h") es entonces apto para la recolección de elementos.

string b = "h";
b += "ello";

El operador [] puede usarse para el acceso de solo lectura a determinados caracteres de una cadena. Los valores válidos comienzan por 0 y deben ser menores que la longitud de la cadena:

string str = "test";
char x = str[2];  // x = 's';

De igual manera, el operador [] también puede usarse para recorrer en iteración cada carácter en una cadena:

string str = "test";

for (int i = 0; i < str.Length; i++)
{
  Console.Write(str[i] + " ");
}
// Output: t e s t

Literales de cadena

Los literales de cadena son de tipo string y se pueden escribir de tres formas: sin formato, entre comillas y textuales.

Los literales de cadena sin formato están disponibles a partir de C# 11. Los literales de cadena sin formato pueden contener texto arbitrario sin necesidad de secuencias de escape. Los literales de cadena sin formato pueden incluir espacios en blanco, nuevas líneas, comillas insertadas y otros caracteres especiales. Los literales de cadena sin formato se incluyen entre tres comillas dobles como mínimo ("""):

"""
This is a multi-line
    string literal with the second line indented.
"""

Incluso puede incluir una secuencia de tres caracteres (o más) de comillas dobles. Si el texto requiere una secuencia de comillas insertada, se inicia y finaliza el literal de cadena sin formato con más comillas, según sea necesario:

"""""
This raw string literal has four """", count them: """" four!
embedded quote characters in a sequence. That's why it starts and ends
with five double quotes.

You could extend this example with as many embedded quotes as needed for your text.
"""""

Los literales de cadena sin formato suelen tener las secuencias de comillas iniciales y finales en líneas independientes del texto insertado. Los literales de cadena sin formato de varias líneas admiten cadenas entre comillas:

var message = """
"This is a very important message."
""";
Console.WriteLine(message);
// output: "This is a very important message."

Cuando las comillas iniciales y finales están en líneas distintas, las líneas nuevas después de la comilla inicial y final no se incluyen en el contenido final. La secuencia de comillas de cierre dictamina la columna situada más a la izquierda del literal de cadena. Puede aplicar sangría a un literal de cadena sin formato para que coincida con el formato de código general:

var message = """
    "This is a very important message."
    """;
Console.WriteLine(message);
// output: "This is a very important message."
// The leftmost whitespace is not part of the raw string literal

Se conservan las columnas a la derecha de la secuencia de comillas finales. Este comportamiento permite cadenas sin procesar para formatos de datos como JSON, YAML o XML, como se muestra en el ejemplo siguiente:

var json= """
    {
        "prop": 0
    }
    """;

El compilador emite un error si alguna de las líneas de texto se extiende a la izquierda de la secuencia de comillas de cierre. Las secuencias de comillas de apertura y cierre pueden estar en la misma línea, siempre y cuando el literal de cadena no comience ni termine con un carácter de comilla:

var shortText = """He said "hello!" this morning.""";

Puede combinar literales de cadena sin formato con interpolación de cadenas para incluir caracteres de comilla y llaves en la cadena de salida.

Los literales de cadena se incluyen entre comillas dobles ("):

"good morning"  // a string literal

Los literales de cadena pueden contener cualquier literal de carácter. Se incluyen secuencias de escape. En el ejemplo siguiente se usa una secuencia de escape \\ para la barra diagonal inversa, \u0066 para la letra f y \n para la nueva línea.

string a = "\\\u0066\n F";
Console.WriteLine(a);
// Output:
// \f
//  F

Nota

El código de escape \udddd (donde dddd es un número de cuatro dígitos) representa el carácter Unicode U+dddd. También se reconocen los códigos de escape Unicode de 8 dígitos: \Udddddddd.

Los literales de cadena textual empiezan por @ y también se incluyen entre comillas dobles. Por ejemplo:

@"good morning"  // a string literal

La ventaja de las cadenas textuales es que las secuencias de escape no se procesan, lo que facilita la escritura. Por ejemplo, el texto siguiente coincide con un nombre de archivo completo de Windows:

@"c:\Docs\Source\a.txt"  // rather than "c:\\Docs\\Source\\a.txt"

Para incluir una comilla doble en una cadena @entrecomillada, duplique esto:

@"""Ahoy!"" cried the captain." // "Ahoy!" cried the captain.

Literales de cadena de UTF-8

Las cadenas de .NET se almacenan mediante codificación UTF-16. UTF-8 es el estándar para los protocolos web y otras bibliotecas importantes. A partir de C# 11, puede agregar el u8 sufijo a un literal de cadena para especificar la codificación UTF-8. Los literales UTF-8 se almacenan como ReadOnlySpan<byte> objetos. El tipo natural de un literal de cadena UTF-8 es ReadOnlySpan<byte>. El uso de un literal de cadena UTF-8 crea una declaración más clara que declarar el equivalente System.ReadOnlySpan<T>, como se muestra en el código siguiente:

ReadOnlySpan<byte> AuthWithTrailingSpace = new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 };
ReadOnlySpan<byte> AuthStringLiteral = "AUTH "u8;

Para almacenar un literal de cadena UTF-8 como una matriz, se requiere el uso de ReadOnlySpan<T>.ToArray() para copiar los bytes que contienen el literal en la matriz mutable:

byte[] AuthStringLiteral = "AUTH "u8.ToArray();

Los literales de cadena UTF-8 no son constantes en tiempo de compilación; son constantes en tiempo de ejecución. Por lo tanto, no se pueden usar como valor predeterminado para un parámetro opcional. Los literales de cadena UTF-8 no se pueden combinar con interpolación de cadenas. No se puede usar el $ token y el u8 sufijo en la misma expresión de cadena.

Tipo delegate

La declaración de un tipo delegado es similar a una firma de método. Tiene un valor devuelto y un número cualquiera de parámetros de cualquier tipo:

public delegate void MessageDelegate(string message);
public delegate int AnotherDelegate(MyType m, long num);

En. NET, los tipos System.Action y System.Func proporcionan definiciones genéricas para muchos delegados comunes. Es probable que no sea necesario definir nuevos tipos delegados personalizados. En su lugar, puede crear instancias de los tipos genéricos proporcionados.

Un delegate es un tipo de referencia que puede usarse para encapsular un método con nombre o anónimo. Los delegados son similares a los punteros de función en C++; pero son más seguros y proporcionan mayor seguridad de tipos. Para las aplicaciones de delegados, vea Delegados y Delegados genéricos. Los delegados son la base de los eventos. Se pueden crear instancias de un delegado asociándolo a un método con nombre o anónimo.

Para crear instancias del delegado debe usarse un método o una expresión lambda que tenga un tipo de valor devuelto y parámetros de entrada compatibles. Para obtener más información sobre el grado de variación permitida en la firma de método, vea Varianza en delegados. Para el uso con métodos anónimos, el delegado y el código que se van a asociar se declaran juntos.

Se produce un error en la combinación y eliminación de delegados con una excepción en tiempo de ejecución cuando los tipos delegados implicados en el tiempo de ejecución son diferentes debido a la conversión de variantes. En el ejemplo siguiente se muestra una situación que produce un error:

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Valid due to implicit reference conversion of
// objectAction to Action<string>, but may fail
// at run time.
Action<string> combination = stringAction + objectAction;

Puede crear un delegado con el tipo de tiempo de ejecución correcto mediante la creación de un objeto delegado. En el ejemplo siguiente se muestra cómo se puede aplicar esta solución alternativa al ejemplo anterior.

Action<string> stringAction = str => {};
Action<object> objectAction = obj => {};
  
// Creates a new delegate instance with a runtime type of Action<string>.
Action<string> wrappedObjectAction = new Action<string>(objectAction);

// The two Action<string> delegate instances can now be combined.
Action<string> combination = stringAction + wrappedObjectAction;

Puede declarar punteros de función, que usan una sintaxis similar. Un puntero de función usa la instrucción calli en lugar de crear instancias de un tipo delegado y llamar al método virtual Invoke.

Tipo dynamic

El tipo dynamic indica el uso de la variable y las referencias a su comprobación de tipos en el tiempo de compilación de omisión de miembros. En su lugar, se resuelven estas operaciones en tiempo de ejecución. El tipo dynamic simplifica el acceso a las API de COM como las API de automatización de Office, a API dinámicas como las bibliotecas de IronPython, y a Document Object Model (DOM) HTML.

El tipo dynamic se comporta como el tipo object en la mayoría de las circunstancias. En concreto, se puede convertir cualquier expresión no NULL para el tipo dynamic. El tipo dynamic se diferencia de object en que el compilador no resuelve o no comprueba el tipo de las operaciones que contienen expresiones de tipo dynamic. El compilador empaqueta información sobre la operación y esa información se usa después para evaluar la operación en tiempo de ejecución. Como parte del proceso, las variables de tipo dynamic están compiladas en las variables de tipo object. Por consiguiente, el tipo dynamic solo existe en tiempo de compilación, no en tiempo de ejecución.

En el siguiente ejemplo se contrasta una variable de tipo dynamic con una variable de tipo object. Para comprobar el tipo de cada variable en tiempo de compilación, coloque el puntero del mouse sobre dyn u obj en las instrucciones WriteLine. Copie el código siguiente en un editor donde IntelliSense esté disponible. IntelliSense muestra dynamic para dyn y object para obj.

class Program
{
    static void Main(string[] args)
    {
        dynamic dyn = 1;
        object obj = 1;

        // Rest the mouse pointer over dyn and obj to see their
        // types at compile time.
        System.Console.WriteLine(dyn.GetType());
        System.Console.WriteLine(obj.GetType());
    }
}

Las instrucciones WriteLine muestran los tipos en tiempo de ejecución de dyn y obj. En ese punto, ambos tienen el mismo tipo, entero. Se produce el siguiente resultado:

System.Int32
System.Int32

Para ver la diferencia entre dyn y obj en tiempo de compilación, agregue las dos líneas siguientes entre las declaraciones y las instrucciones WriteLine en el ejemplo anterior.

dyn = dyn + 3;
obj = obj + 3;

Un error del compilador se notifica para el intento de suma de un entero y un objeto en la expresión obj + 3. En cambio, no se notifica ningún error para dyn + 3. En tiempo de compilación no se comprueba la expresión que contiene dyn porque el tipo de dyn es dynamic.

El ejemplo siguiente usa dynamic en varias declaraciones. El método Main también contrasta la comprobación de tipo en tiempo de compilación con la comprobación de tipo en tiempo de ejecución.

using System;

namespace DynamicExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            ExampleClass ec = new ExampleClass();
            Console.WriteLine(ec.ExampleMethod(10));
            Console.WriteLine(ec.ExampleMethod("value"));

            // The following line causes a compiler error because ExampleMethod
            // takes only one argument.
            //Console.WriteLine(ec.ExampleMethod(10, 4));

            dynamic dynamic_ec = new ExampleClass();
            Console.WriteLine(dynamic_ec.ExampleMethod(10));

            // Because dynamic_ec is dynamic, the following call to ExampleMethod
            // with two arguments does not produce an error at compile time.
            // However, it does cause a run-time error.
            //Console.WriteLine(dynamic_ec.ExampleMethod(10, 4));
        }
    }

    class ExampleClass
    {
        static dynamic _field;
        dynamic Prop { get; set; }

        public dynamic ExampleMethod(dynamic d)
        {
            dynamic local = "Local variable";
            int two = 2;

            if (d is int)
            {
                return local;
            }
            else
            {
                return two;
            }
        }
    }
}
// Results:
// Local variable
// 2
// Local variable

Especificación del lenguaje C#

Para más información, vea las secciones siguientes de la Especificación del lenguaje C#:

Consulte también