# data[].attributes.body
Version C# 1.0 Lanzado en 2002 acompañado de las versiones de Visual estudio
7.0 y 7.1

# data[].attributes.attachment_link.readable_text
0:00 / 1:07•Watch full video

# data[].attributes.attachment_link.title
1.0 - c# - SetUp Bunifu Framework , Visual Studio.   (OLDER VERSION)

# data[].attributes.body
C# 2.0 Lanzado en el 2005
Acompañado de las Versiones de Visual Studio:
- 7.1 Lanzado 2003
- 8.0 Lanzado 2005

# data[].attributes.attachment_link.title
MongoDB from C# with the .NET driver 2.0

# data[].attributes.body
c# 3.0 linq E+F .NET Ver 3.0/3.5 visual studio 2008

# data[].attributes.body
c# 4.0 dinamic programming +TPL .NET Ver 4.0 visual studio 2010

# data[].attributes.attachment_link.title
Host Integration Tools For Microsoft Version 4.0 - What's New?

# data[].attributes.body
3c# 5.0 asynchronous programming .NET Ver 4.5 visual studio 2012/2013

# data[].attributes.attachment_link.title
Course Preview: C# Fundamentals with C# 5.0

# data[].attributes.body
on las versiones 3.0 y 5.0, C# había agregado importantes funciones nuevas en un lenguaje orientado a objetos. Con la versión 6.0, lanzada con Visual Studio 2015, dejaría de ser una función dominante y, en su lugar, lanzaría muchas funciones más pequeñas que hicieron que la programación en C# fuera más productiva. Éstos son algunos de ellos:

# data[].attributes.body
se lanzó con Visual Studio 2017. Esta versión tiene algunas cosas geniales y evolutivas en la línea de C# 6.0, pero sin el compilador como servicio.

# data[].attributes.body
C# 8.0 es la primera versión importante de C# que apunta específicamente a .NET Core. Algunas funciones se basan en nuevas capacidades de CLR, otras en tipos de biblioteca agregados solo en .NET Core

# data[].attributes.attachment_link.readable_text
C# 8.0 agrega las siguientes características y mejoras al lenguaje C#:

C# 8.0 se admite en .NET Core 3.x y .NET Standard 2.1. Para obtener más información, vea Control de versiones del lenguaje C#.

En el resto de este artículo se describen brevemente estas características. Cuando hay disponibles artículos detallados, se proporcionan vínculos a esos tutoriales e introducciones. Puede explorar estas características en su entorno mediante la herramienta global dotnet try:

1. Instale la herramienta global dotnet-try.

2. Clone el repositorio dotnet/try-samples.

3. Establezca el directorio actual en el subdirectorio csharp8 para el repositorio try-samples.

4. Ejecute dotnet try.

Miembros de solo lectura

public struct Point { public double X { get; set; } public double Y { get; set; } public double Distance => Math.Sqrt(X * X + Y * Y); public override string ToString() => $"({X}, {Y}) is {Distance} from the origin"; }

public readonly override string ToString() => $"({X}, {Y}) is {Distance} from the origin";

warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an implicit copy of 'this'

El compilador le advierte cuando es necesario crear una copia defensiva. La propiedad Distance no cambia el estado, por lo que puede corregir esta advertencia si agrega el modificador readonly a la declaración:

public readonly double Distance => Math.Sqrt(X * X + Y * Y);

Tenga en cuenta que el modificador readonly es necesario en una propiedad de solo lectura. El compilador no presupone que los descriptores de acceso get no modifican el estado; debe declarar readonly explícitamente. Las propiedades implementadas automáticamente son una excepción; el compilador tratará todos los captadores implementados automáticamente como readonly, por lo que aquí no es necesario agregar el modificador readonly a las propiedades X e Y.

El compilador aplica la regla por la que los miembros readonly no modifican el estado. El método siguiente no se compilará a menos que se quite el modificador readonly:

public readonly void Translate(int xOffset, int yOffset) { X += xOffset; Y += yOffset; }

Esta característica permite especificar la intención del diseño para que el compilador pueda aplicarla, y realizar optimizaciones basadas en dicha intención.

Para obtener más información, vea la sección Miembros de instancia del artículo Tipos de estructura.

Métodos de interfaz predeterminados

Ahora puede agregar miembros a interfaces y proporcionar una implementación de esos miembros. Esta característica del lenguaje permite que los creadores de API agreguen métodos a una interfaz en versiones posteriores sin interrumpir la compatibilidad binaria o de origen con implementaciones existentes de dicha interfaz. Las implementaciones existentes heredan la implementación predeterminada. Esta característica también permite que C# interopere con las API que tienen como destino Android o Swift, que admiten características similares. Los métodos de interfaz predeterminados también permiten escenarios similares a una característica del lenguaje de "rasgos".

Los métodos de interfaz predeterminados afectan a muchos escenarios y elementos del lenguaje. Nuestro primer tutorial abarca la actualización de una interfaz con implementaciones predeterminadas.

Más patrones en más lugares

La coincidencia de patrones ofrece herramientas que proporcionan funcionalidad dependiente de la forma entre tipos de datos relacionados pero diferentes. C# 7.0 introdujo la sintaxis de patrones de tipos y patrones de constantes mediante la expresión y la instrucción . Estas características representaban los primeros pasos hacia el uso de paradigmas de programación donde los datos y la funcionalidad están separados. A medida que el sector se mueve hacia más microservicios y otras arquitecturas basadas en la nube, se necesitan otras herramientas de lenguaje.

C# 8.0 amplía este vocabulario para que pueda usar más expresiones de patrones en más lugares del código. Cuando los datos y la funcionalidad estén separados, tenga en cuenta estas características. Cuando los algoritmos dependan de un hecho distinto del tipo de entorno de ejecución de un objeto, tenga en cuenta la coincidencia de patrones. Estas técnicas ofrecen otra forma de expresar los diseños.

Además de nuevos patrones en nuevos lugares C# 8.0 agrega patrones recursivos. Los patrones recursivos son patrones que pueden contener otros patrones.

Expresiones switch

Con frecuencia, una instrucción genera un valor en cada uno de sus bloques case. Las expresiones switch le permiten usar una sintaxis de expresiones más concisa. Hay menos palabras clave case y break repetitivas y menos llaves. Por ejemplo, considere la siguiente enumeración que muestra los colores del arco iris:

public enum Rainbow { Red, Orange, Yellow, Green, Blue, Indigo, Violet }

Si la aplicación definió un tipo RGBColor construido a partir de los componentes R, G y B, podría convertir un valor Rainbow a sus valores RGB con el método siguiente que contiene una expresión switch:

public static RGBColor FromRainbow(Rainbow colorBand) => colorBand switch { Rainbow.Red => new RGBColor(0xFF, 0x00, 0x00), Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00), Rainbow.Yellow => new RGBColor(0xFF, 0xFF, 0x00), Rainbow.Green => new RGBColor(0x00, 0xFF, 0x00), Rainbow.Blue => new RGBColor(0x00, 0x00, 0xFF), Rainbow.Indigo => new RGBColor(0x4B, 0x00, 0x82), Rainbow.Violet => new RGBColor(0x94, 0x00, 0xD3), _ => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)), };

Aquí hay varias mejoras en la sintaxis:

La variable precede a la palabra clave switch. El orden diferente permite distinguir fácilmente la expresión switch de la instrucción switch.

Los elementos case y : se reemplazan por =>. Es más concisa e intuitiva.

El caso default se reemplaza por un descarte _.

Los cuerpos son expresiones, no instrucciones.

Compárelo con el código equivalente mediante la instrucción clásica switch:

public static RGBColor FromRainbowClassic(Rainbow colorBand) { switch (colorBand) { case Rainbow.Red: return new RGBColor(0xFF, 0x00, 0x00); case Rainbow.Orange: return new RGBColor(0xFF, 0x7F, 0x00); case Rainbow.Yellow: return new RGBColor(0xFF, 0xFF, 0x00); case Rainbow.Green: return new RGBColor(0x00, 0xFF, 0x00); case Rainbow.Blue: return new RGBColor(0x00, 0x00, 0xFF); case Rainbow.Indigo: return new RGBColor(0x4B, 0x00, 0x82); case Rainbow.Violet: return new RGBColor(0x94, 0x00, 0xD3); default: throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)); }; }

Para obtener más información, vea expresión .

Patrones de propiedades

El patrón de propiedades permite hallar la coincidencia con las propiedades del objeto examinado. Considere un sitio de comercio electrónico que debe calcular los impuestos de ventas según la dirección del comprador. Ese cálculo no es una responsabilidad fundamental de una clase Address. Cambiará con el tiempo, probablemente con más frecuencia que el formato de dirección. El importe de los impuestos de ventas depende de la propiedad State de la dirección. El método siguiente usa el patrón de propiedades para calcular los impuestos de ventas a partir de la dirección y el precio:

public static decimal ComputeSalesTax(Address location, decimal salePrice) => location switch { { State: "WA" } => salePrice * 0.06M, { State: "MN" } => salePrice * 0.075M, { State: "MI" } => salePrice * 0.05M, // other cases removed for brevity... _ => 0M };

La coincidencia de patrones crea una sintaxis concisa para expresar este algoritmo.

Para obtener más información, vea la sección Patrón de propiedad del artículo Patrones.

Patrones de tupla

Algunos algoritmos dependen de varias entradas. Los patrones de tupla permiten hacer cambios en función de varios valores, expresados como una tupla. El código siguiente muestra una expresión switch del juego piedra, papel, tijeras:

public static string RockPaperScissors(string first, string second) => (first, second) switch { ("rock", "paper") => "rock is covered by paper. Paper wins.", ("rock", "scissors") => "rock breaks scissors. Rock wins.", ("paper", "rock") => "paper covers rock. Paper wins.", ("paper", "scissors") => "paper is cut by scissors. Scissors wins.", ("scissors", "rock") => "scissors is broken by rock. Rock wins.", ("scissors", "paper") => "scissors cuts paper. Scissors wins.", (_, _) => "tie" };

Los mensajes indican el ganador. El caso de descarte representa las tres combinaciones de valores equivalentes, u otras entradas de texto.

Patrones posicionales

Algunos tipos incluyen un método Deconstruct que deconstruye sus propiedades en variables discretas. Cuando se Deconstruct puede acceder a un método, puede usar patrones Deconstruct para inspeccionar las propiedades del objeto y usar esas propiedades para un patrón. Tenga en cuenta la siguiente clase Point que incluye un método Deconstruct con el objetivo de crear variables discretas para X y Y:

public class Point { public int X { get; } public int Y { get; } public Point(int x, int y) => (X, Y) = (x, y); public void Deconstruct(out int x, out int y) => (x, y) = (X, Y); }

Además, tenga en cuenta la siguiente enumeración, que representa diversas posiciones de un cuadrante:

public enum Quadrant { Unknown, Origin, One, Two, Three, Four, OnBorder }

El método siguiente usa el patrón posicional para extraer los valores de y y. A continuación, usa una cláusula when para determinar el valor Quadrant del punto:

static Quadrant GetQuadrant(Point point) => point switch { (0, 0) => Quadrant.Origin, var (x, y) when x > 0 && y > 0 => Quadrant.One, var (x, y) when x < 0 && y > 0 => Quadrant.Two, var (x, y) when x < 0 && y < 0 => Quadrant.Three, var (x, y) when x > 0 && y < 0 => Quadrant.Four, var (_, _) => Quadrant.OnBorder, _ => Quadrant.Unknown };

El patrón de descarte del modificador anterior coincide cuando x o y es 0, pero no ambos. Una expresión switch debe generar un valor o producir una excepción. Si ninguno de los casos coincide, la expresión switch produce una excepción. El compilador genera una advertencia si no cubre todos los posibles casos en la expresión switch.

Puede explorar las técnicas de coincidencia de patrones en este tutorial avanzado sobre la coincidencia de patrones. Para obtener más información sobre un patrón posicional, vea la sección Patrón posicional del artículo Patrones.

Declaraciones using

Una declaración using es una declaración de variable precedida de la palabra clave . Indica al compilador que la variable que se declara debe eliminarse al final del ámbito de inclusión. Por ejemplo, considere el siguiente código que escribe un archivo de texto:

static int WriteLinesToFile(IEnumerable<string> lines) { using var file = new System.IO.StreamWriter("WriteLines2.txt"); int skippedLines = 0; foreach (string line in lines) { if (!line.Contains("Second")) { file.WriteLine(line); } else { skippedLines++; } } // Notice how skippedLines is in scope here. return skippedLines; // file is disposed here }

En el ejemplo anterior, el archivo se elimina cuando se alcanza la llave de cierre del método. Ese es el final del ámbito en el que se declara file. El código anterior es equivalente al siguiente código que usa las instrucciones using clásicas:

static int WriteLinesToFile(IEnumerable<string> lines) { using (var file = new System.IO.StreamWriter("WriteLines2.txt")) { int skippedLines = 0; foreach (string line in lines) { if (!line.Contains("Second")) { file.WriteLine(line); } else { skippedLines++; } } return skippedLines; } // file is disposed here }

En el ejemplo anterior, el archivo se elimina cuando se alcanza la llave de cierre asociada con la instrucción using.

En ambos casos, el compilador genera la llamada a Dispose(). El compilador genera un error si la expresión de la instrucción using no se puede eliminar.

Funciones locales estáticas

Observe el código siguiente. La función local LocalFunction accede a la variable y, declarada en el ámbito de inclusión (el método M). Por lo tanto, LocalFunction no se puede declarar con el modificador static:

int M() { int y; LocalFunction(); return y; void LocalFunction() => y = 0; }

El código siguiente contiene una función local estática. Puede ser estática porque no accede a las variables del ámbito de inclusión:

int M() { int y = 5; int x = 7; return Add(x, y); static int Add(int left, int right) => left + right; }

Estructuras ref descartables

Un elemento struct declarado con el modificador ref no puede implementar ninguna interfaz y, por tanto, no puede implementar IDisposable. Por lo tanto, para permitir que se elimine un elemento ref struct, debe tener un método void Dispose() accesible. Esta característica también se aplica a las declaraciones readonly ref struct.

Tipos de referencia que aceptan valores NULL

Dentro de un contexto de anotación que acepta valores NULL, cualquier variable de un tipo de referencia se considera un tipo de referencia que no acepta valores NULL. Si desea indicar que una variable puede ser NULL, ? debe anexar el nombre de tipo con para declarar la variable como un tipo de ?.

En los tipos de referencia que no aceptan valores NULL, el compilador usa el análisis de flujo para garantizar que las variables locales se inicializan en un valor no NULL cuando se declaran. Los campos se deben inicializar durante la construcción. Si la variable no se establece mediante una llamada a alguno de los constructores disponibles o por medio de un inicializador, el compilador genera una advertencia. Además, los tipos de referencia que no aceptan valores NULL no pueden tener asignado un valor que podría ser NULL.

Los tipos de referencia que aceptan valores NULL no se comprueban para garantizar que se asignan o inicializan como NULL. Sin embargo, el compilador usa el análisis de flujo para garantizar que cualquier variable de un tipo de referencia que acepta valores NULL se compara con NULL antes de acceder a ella o de asignarse a un tipo de referencia que no los acepta.

Puede aprender más sobre la característica en la introducción a los tipos de referencia que aceptan valores NULL. Pruébela por su cuenta en una nueva aplicación en este tutorial de tipos de referencia que aceptan valores NULL. Puede encontrar más información sobre los pasos para migrar una base de código existente para hacer uso de los tipos de referencia que aceptan valores NULL en el artículo sobre la actualización a tipos de referencia que aceptan valores NULL.

Secuencias asincrónicas

A partir de C# 8.0, puede crear y consumir secuencias de forma asincrónica. Un método que devuelve una secuencia asincrónica tiene tres propiedades:

1. Se declara con el modificador async.

2. Devuelve IAsyncEnumerable<T>.

3. El método contiene instrucciones yield return que devuelven elementos sucesivos de la secuencia asincrónica.

Para consumir una secuencia asincrónica es necesario agregar la palabra clave await delante de la palabra clave foreach al enumerar los elementos de la secuencia. Para agregar la palabra clave await es necesario declarar el método que enumera la secuencia asincrónica con el modificador async y devolver un tipo permitido para un método async. Normalmente, esto significa devolver Task o Task<TResult>. También puede ser ValueTask o ValueTask<TResult>. Un método puede consumir y producir una secuencia asincrónica, lo que significa que devolvería IAsyncEnumerable<T>. El siguiente código genera una secuencia de 0 a 19, con una espera de 100 ms entre la generación de cada número:

public static async System.Collections.Generic.IAsyncEnumerable<int> GenerateSequence() { for (int i = 0; i < 20; i++) { await Task.Delay(100); yield return i; } }

Se enumeraría la secuencia mediante la instrucción await foreach:

await foreach (var number in GenerateSequence()) { Console.WriteLine(number); }

Puede probar secuencias asincrónicas por su cuenta en nuestro tutorial sobre la creación y consumo de secuencias asincrónicas. Los elementos de secuencia se procesan de forma predeterminada en el contexto capturado. Si quiere deshabilitar la captura del contexto, use el método de extensión TaskAsyncEnumerableExtensions.ConfigureAwait. Para obtener más información sobre los contextos de sincronización y la captura del contexto actual, vea el artículo sobre el consumo del patrón asincrónico basado en tareas.

Asincrónica descartable

A partir C# 8.0, el lenguaje admite tipos descartables asincrónicos que implementan la interfaz System.IAsyncDisposable. Use la instrucción await using para trabajar con un objeto descartable de forma asincrónica. Para obtener más información, vea el artículo Implementación de un método DisposeAsync.

Índices y rangos

Los índices y rangos proporcionan una sintaxis concisa para acceder a elementos únicos o intervalos en una secuencia.

Esta compatibilidad con lenguajes se basa en dos nuevos tipos y dos nuevos operadores:

System.Index representa un índice en una secuencia.

El índice desde el operador final ^, que especifica que un índice es relativo al final de la secuencia.

System.Range representa un subrango de una secuencia.

El operador de intervalo .., que especifica el inicio y el final de un intervalo como sus operandos.

Comencemos con las reglas de índices. Considere un elemento sequence de matriz. El índice 0 es igual que sequence[0]. El índice ^0 es igual que sequence[sequence.Length]. Tenga en cuenta que sequence[^0] produce una excepción, al igual que sequence[sequence.Length]. Para cualquier número n, el índice ^n es igual que sequence.Length - n.

Un rango especifica el inicio y el final de un intervalo. El inicio del rango es inclusivo, pero su final es exclusivo, lo que significa que el inicio se incluye en el rango, pero el final no. El rango [0..^0] representa todo el intervalo, al igual que [0..sequence.Length] representa todo el intervalo.

Veamos algunos ejemplos. Tenga en cuenta la siguiente matriz, anotada con su índice desde el principio y desde el final:

var words = new string[] { // index from start index from end "The", // 0 ^9 "quick", // 1 ^8 "brown", // 2 ^7 "fox", // 3 ^6 "jumped", // 4 ^5 "over", // 5 ^4 "the", // 6 ^3 "lazy", // 7 ^2 "dog" // 8 ^1 }; // 9 (or words.Length) ^0

Puede recuperar la última palabra con el índice ^1:

Console.WriteLine($"The last word is {words[^1]}"); // writes "dog"

var quickBrownFox = words[1..4];

var lazyDog = words[^2..^0];

En los ejemplos siguientes se crean rangos con final abierto para el inicio, el final o ambos:

var allWords = words[..]; // contains "The" through "dog". var firstPhrase = words[..4]; // contains "The" through "fox" var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"

También puede declarar rangos como variables:

Range phrase = 1..4;

El rango se puede usar luego dentro de los caracteres [ y ]:

var text = words[phrase];

No solo las matrices admiten índices y rangos. También puede usar índices e intervalos con cadena, o ReadOnlySpan<T>. Para más información, consulte Compatibilidad con tipos para los índices y los rangos.

Puede explorar más información acerca de los índices y los intervalos en el tutorial sobre índices e intervalos.

Asignación de uso combinado de NULL

List<int> numbers = null; int? i = null; numbers ??= new List<int>(); numbers.Add(i ??= 17); numbers.Add(i ??= 20); Console.WriteLine(string.Join(" ", numbers)); // output: 17 17 Console.WriteLine(i); // output: 17

Para obtener más información, consulte Operadores ?? y ??.

Tipos construidos no administrados

En C# 7.3 y versiones anteriores, un tipo construido (es decir, un tipo que incluye al menos un argumento de tipo) no puede ser un tipo no administrado. A partir de C# 8.0, un tipo de valor construido no está administrado si solo contiene campos de tipos no administrados.

Por ejemplo, dada la siguiente definición del tipo genérico Coords<T>:

public struct Coords<T> { public T X; public T Y; }

el tipo Coords<int> es un tipo no administrado en C# 8.0 y versiones posteriores. Al igual que en el caso de cualquier tipo no administrado, puede crear un puntero a una variable de este tipo o asignar un bloque de memoria en la pila para las instancias de este tipo:

Span<Coords<int>> coordinates = stackalloc[] { new Coords<int> { X = 0, Y = 0 }, new Coords<int> { X = 0, Y = 3 }, new Coords<int> { X = 4, Y = 0 } };

Para más información, consulte Tipos no administrados.

Stackalloc en expresiones anidadas

A partir de C# 8.0, si el resultado de una expresión stackalloc es del tipo o System.ReadOnlySpan<T> , stackalloc puede usar la expresión en otras expresiones:

Span<int> numbers = stackalloc[] { 1, 2, 3, 4, 5, 6 }; var ind = numbers.IndexOfAny(stackalloc[] { 2, 4, 6, 8 }); Console.WriteLine(ind); // output: 1

Mejora de las cadenas textuales interpoladas

El orden de los $ tokens y @ en $ textuales interpoladas puede ser cualquiera: $@"..."@$"..." y son cadenas textuales interpoladas válidas. En versiones de C# anteriores, el token $ debe aparecer delante del token @.

# data[].attributes.attachment_link.description
C# 8.0 agrega las siguientes características y mejoras al lenguaje C#: C# 8.0 se admite en .NET Core 3.x y .NET Standard 2.1. Para obtener más información, vea Control de versiones del lenguaje C#. En el resto de este artículo se describen brevemente estas características.