¿Cuáles son las nuevas funciones de C#8 admitidas en Unity2020.2?

La Academia Hongliu te hará unos pasos más rápido. Hola, soy Zheng Hongzhi, su pionero de la tecnología, puede llamarme Dazhi.

C# 8.0 agrega las siguientes funciones y mejoras al lenguaje C#:

  • Miembros de solo lectura
  • método de interfaz predeterminado
  • Mejoras en la coincidencia de patrones:
    • cambio de expresión
    • modo de atributo
    • patrón de tupla
    • modo de ubicación
  • Declaración de uso
  • función local estática
  • estructura ref desechable
  • tipo de referencia anulable
  • flujo asíncrono
  • asíncrono liberable
  • Índices y Rangos
  • Asignación coalescente nula
  • tipo construido no administrado
  • Stackalloc en expresiones anidadas
  • Mejoras para interpolar cadenas textuales

Miembros de solo lectura

readonlyEl modificador se puede aplicar a los miembros de una estructura. Indica que el miembro no modificará el estado. Esto readonlyes structmás granular que aplicar el modificador a la declaración. Considere la siguiente estructura mutable:

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";
}

Como la mayoría de las estructuras, ToString()los métodos no modifican el estado. Esto se puede indicar agregando readonlyel modificador a la declaración de:ToString()

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

El cambio anterior genera una advertencia del compilador porque ToStringaccede a una propiedadreadonly que no está marcada como:Distance

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

El compilador emitirá una advertencia cuando sea necesario crear una copia defensiva. DistanceLos atributos no cambian de estado, por lo que esta advertencia se puede solucionar agregando readonlyel modificador a la declaración:

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

Tenga en cuenta que readonlyse requieren modificadores para las propiedades de solo lectura. El compilador supondrá que getun descriptor de acceso puede modificar el estado; debe declararse explícitamente readonly. Una excepción son las propiedades implementadas automáticamente; el compilador trata a todos los captadores implementados automáticamente como tales , por lo que no es necesario agregar modificadores aquíreadonly .XYreadonly

El compilador hace cumplir la regla de que readonlylos miembros no modifican el estado. Los siguientes métodos no se compilarán a menos que se elimine readonlyel modificador :

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

Esta función permite especificar la intención del diseño para que el compilador pueda aplicar esa intención y optimizar en función de esa intención.

método de interfaz predeterminado

Ahora puede agregar miembros a la interfaz y proporcionar implementaciones para esos miembros. Esta función de lenguaje permite a los autores de API agregar métodos a futuras versiones de una interfaz sin romper la compatibilidad binaria o de fuente con la implementación actual de la interfaz. Las implementaciones existentes heredan la implementación predeterminada. Esta característica permite que C# interopere con las API dirigidas a Android o Swift, que admiten una funcionalidad similar. Los métodos de interfaz predeterminados también admiten escenarios similares a la función de lenguaje de "rasgos".

Usa más patrones en más lugares

La coincidencia de patrones proporciona herramientas para proporcionar características relacionadas con la forma en tipos de datos relacionados pero diferentes. C# is7.0 switchintroduce la sintaxis para patrones de tipos y patrones constantes mediante el uso de expresiones y declaraciones. Estas funciones representan un intento inicial de un paradigma de programación que admita la separación de datos y funcionalidad. Se necesitan herramientas de lenguaje adicionales a medida que la industria avanza hacia más microservicios y otras arquitecturas basadas en la nube.

C# 8.0 amplía este vocabulario para que se puedan usar más expresiones de patrones en más lugares del código. Considere usar estas funciones cuando los datos y la funcionalidad estén separados. Considere usar la coincidencia de patrones cuando un algoritmo depende de hechos distintos al tipo de tiempo de ejecución del objeto. Estas técnicas proporcionan otra forma de expresar diseños.

Además de poder usar nuevos patrones en nuevas ubicaciones, C# 8.0 agrega "patrones recursivos". El resultado de cualquier expresión de patrón es una expresión. Un patrón recursivo es simplemente una expresión de patrón aplicada a la salida de otra expresión de patrón.

cambio de expresión

Normalmente, una declaración de cambio produce un valor en cada uno de sus casebloques . Con las expresiones Switch, hay disponible una sintaxis de expresión más concisa. Solo hay unas pocas repeticiones de caselas breakpalabras clave y y las llaves. Tome la siguiente enumeración que enumera los colores del arco iris como ejemplo:

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

Si su aplicación define un tipo construido a partir de y componentes , puede usar el siguiente método con una expresión de cambio para convertir Ra un valor RGB:GBRGBColorRainbow

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 de sintaxis:

  • Las variables preceden switcha la palabra clave . El orden diferente hace que sea fácil distinguir visualmente las expresiones de cambio de las declaraciones de cambio.
  • caseReemplace los elementos y :con =>. Es más limpio e intuitivo.
  • defaultReemplazar instancias con _descartes.
  • El cuerpo es una expresión, no un enunciado.

Compare esto con el código equivalente usando la switchdeclaración :

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));
    };
}

modo de atributo

Con patrones de atributos, es posible hacer coincidir los atributos del objeto que se está inspeccionando. Considere un ejemplo de un sitio de comercio electrónico que debe calcular el impuesto sobre las ventas en función de la dirección del comprador. Este cálculo no es responsabilidad central de Addressla clase . Cambia con el tiempo, probablemente con más frecuencia que los cambios de formato de dirección. El monto del impuesto a las ventas depende de Statelos atributos . El siguiente método calcula el impuesto sobre las ventas a partir de la dirección y el precio utilizando el patrón de atributos:

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.

patrón de tupla

Algunos algoritmos se basan en múltiples entradas. Use el patrón de tupla para alternar en función de múltiples valores representados como una tupla. El siguiente código muestra la expresión de alternancia para el juego "piedra, papel o tijera":

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"
    };

El mensaje indica el ganador. Un descarte significa tres combinaciones de un empate (juego de piedra, papel o tijera) u otra entrada de texto.

modo de ubicación

Algunos tipos contienen Deconstructun método que desestructura sus propiedades en variables discretas. Si tiene acceso Deconstructal método , puede usar el patrón de ubicación para examinar las propiedades del objeto y usar esas propiedades en el patrón. Considere la siguiente Pointclase , que contiene métodos para crearX variables discretas para y :YDeconstruct

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, considere las siguientes enumeraciones que representan las diversas posiciones de los cuadrantes:

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

El siguiente método utiliza el patrón posicional para extraer xlos yvalores de y . Luego, usa whenla cláusula para determinar el punto Quadrant:

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 en el cambio anterior coincide cuando xo es 0 (pero no ambos). yUna expresión Switch debe producir un valor o lanzar una excepción. Si ninguna de estas condiciones coincide, la expresión de cambio generará una excepción. El compilador generará una advertencia si no se cubren todos los casos posibles en la expresión de cambio.

declaración de uso

Una declaración de uso es una declaración de variable precedida por usingla palabra clave . Le indica al compilador que las variables declaradas deben procesarse al final del ámbito adjunto. Tome el siguiente código para escribir un archivo de texto como ejemplo:

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 procesa cuando se alcanza el paréntesis de cierre del método. Este es el final del alcance filedeclarado . El código anterior es equivalente al siguiente código usando la instrucción using clásica:

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 procesa cuando se alcanza el paréntesis de cierre asociado con usingla declaración .

En ambos casos, el compilador generará una llamada Dispose()a . El compilador generará un error si la expresión en usingla declaración no está disponible.

función local estática

staticEl modificador ahora se puede agregar a las funciones locales para garantizar que las funciones locales no capturen (hagan referencia) a ninguna variable del ámbito adjunto. Al hacerlo CS8421, se genera "Una función local estática no puede contener una referencia a".

Considere el siguiente código. Las funciones locales LocalFunctionacceden a Mlas variables declaradas en el ámbito adjunto (método) y. Por lo tanto, no se puede declarar con staticel modificador LocalFunction:

int M()
{
    int y;
    LocalFunction();
    return y;

    void LocalFunction() => y = 0;
}

El siguiente código contiene una función local estática. Puede ser estático ya que no accede a ninguna variable en el ámbito adjunto:

int M()
{
    int y = 5;
    int x = 7;
    return Add(x, y);

    static int Add(int left, int right) => left + right;
}

estructura ref desechable

Un declarado con refel modificador structno puede implementar ninguna interfaz y, por lo tanto, no puede implementar IDisposable, por lo que para poder desecharlo ref structdebe tener un void Dispose()método . Esta funcionalidad también se aplica a readonly ref structlas declaraciones .

tipo de referencia anulable

En el contexto de una anotación anulable, cualquier variable de un tipo de referencia se considera un tipo de referencia no anulable. Para indicar que una variable puede ser nula, se debe agregar el nombre del tipo ?para declarar la variable como un tipo de referencia que acepta valores NULL.

Para los tipos de referencia que no aceptan valores NULL, el compilador utiliza el análisis de flujo para garantizar que las variables locales se inicialicen en valores que no sean NULL en el momento de la declaración. Los campos deben inicializarse durante la construcción. El compilador generará una advertencia si la variable no se establece llamando a cualquiera de los constructores disponibles o mediante una expresión de inicialización. Además, no puede asignar un valor que admite valores NULL a un tipo de referencia que no acepta valores NULL.

Los tipos de referencia que aceptan valores NULL no se comprueban para garantizar que no se les asigne un valor Null o se inicialicen en Null. Sin embargo, el compilador utiliza el análisis de flujo para asegurarse de que cualquier variable de un tipo de referencia que admite valores NULL se compruebe si admite valores NULL antes de acceder a ella o asignarla a un tipo de referencia que no acepta valores NULL.

flujo asíncrono

A partir de C# 8.0, las secuencias se pueden crear y consumir de forma asincrónica. Los métodos que devuelven un flujo asíncrono tienen tres propiedades:

  1. Se declara con asyncel modificador .
  2. Devolverá IAsyncEnumerable.
  3. Este método contiene yield returninstrucciones .

El uso de secuencias asíncronas foreachrequiere el prefijo awaitde la palabra clave al enumerar los elementos de la secuencia. Agregar awaitla palabra clave requiere métodos que enumeren secuencias asíncronas para asyncdeclarar con el modificador y devolver los tipos permitidos por asyncel método . Por lo general, esto significa regresar Tasko Task<TResult>. También puede ser ValueTasko ValueTask<TResult>. Un método puede consumir una transmisión asíncrona o producir una transmisión asíncrona, lo que significa que devolverá IAsyncEnumerable<T>. El siguiente código genera una secuencia del 0 al 19, esperando 100 milisegundos 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;
    }
}

Las secuencias se pueden enumerar usando await foreachla declaración :

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

De forma predeterminada, los elementos de flujo se procesan en el contexto capturado. Si desea deshabilitar la captura de contexto, use el método de extensión TaskAsyncEnumerableExtensions.ConfigureAwait.

## asíncrono liberable

A partir de C# 8.0, el lenguaje admite tipos desechables asincrónicos que implementan la interfaz System.IAsyncDisposable. Los objetos desechables asincrónicos se pueden manejar con await usingla instrucción .

Índices y Rangos

Los índices y los rangos proporcionan una sintaxis concisa para acceder a elementos o rangos individuales en una secuencia.

Este soporte de lenguaje se basa en dos nuevos tipos y dos nuevos operadores:

  • System.IndexRepresenta un índice de secuencia.
  • El índice del ^operador especifica un índice relativo al final de la secuencia.
  • System.RangeRepresenta un subrango de una secuencia.
  • Operador de rango .., utilizado para especificar el inicio y el final de un rango, al igual que los operandos.

Comencemos con las reglas de indexación. Considere las matrices sequence. 0El índice es sequence[0]el mismo que . ^0El índice es sequence[sequence.Length]el mismo que . Tenga en cuenta que sequence[^0]no se lanza ninguna excepción, al sequence[sequence.Length]igual que . Para cualquier número n, el índice ^nes sequence.Length - nel mismo que .

rango especifica el inicio y el final del rango. Incluya el inicio del rango pero no el final del rango, lo que significa que el rango incluye el inicio pero no el final. Rango [0..^0]significa todo el rango, al igual que [0..sequence.Length]significa todo el rango.

Eche un vistazo a algunos ejemplos a continuación. Considere la siguiente matriz, anotada con sus índices ascendentes y descendentes:


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

La última palabra se puede recuperar usando ^1el índice :


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

El siguiente código crea un subrango que contiene las palabras "rápido", "marrón" y "zorro". Incluye words[1]a words[3]. El elemento words[4]no está en el rango.

var quickBrownFox = words[1..4];

El siguiente código crea un subrango usando "perezoso" y "perro". Incluye words[^2]y words[^1]. Índice final words[^0]no incluido:

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

El siguiente ejemplo crea rangos abiertos para inicio y/o fin:

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

Además, los ámbitos se pueden declarar como variables:

Range phrase = 1..4;

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

var text = words[phrase];

No solo las matrices admiten la indexación y los rangos. También puede usar índices y rangos con cadenas, Spans o ReadOnlySpans.

Asignación coalescente nula

C# 8.0 introdujo el operador de asignación de fusión nula ??=. El operador se puede usar para asignar el valor de su operando derecho al operando izquierdo solo nullsi .??=

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

tipo construido no administrado

En C# 7.3 y versiones anteriores, un tipo construido (un tipo que contiene al menos un parámetro de tipo) no puede ser un tipo no administrado. A partir de C# 8.0, si un tipo de valor construido contiene solo campos de un tipo no administrado, el tipo no está administrado.

Por ejemplo, supongamos que el Coords<T>tipo tiene la siguiente definición

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

Coords<int>El tipo es un tipo no administrado en C# 8.0 y versiones posteriores. Al igual que con cualquier tipo no administrado, es posible crear punteros a variables de este tipo o asignar bloques de memoria en la pila para 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 }
};

stackalloc en expresiones anidadas

A partir de C# 8.0, si el resultado de una expresión stackalloc es del tipo System.Span o System.ReadOnlySpan, puede usar stackallocla expresión :

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

Mejoras para interpolar cadenas textuales

El orden de los tokens $y @puede ser arbitrario: $@"..."y @$"..."son cadenas textuales interpoladas válidas. En versiones anteriores de C#, $la etiqueta tenía que aparecer antes @que la etiqueta .

[Aprendizaje extendido] Responda en la cuenta oficial de la Academia Hongliuruntime para obtener todos los artículos de esta serie.


Soy Dazhi (vx: zhz11235), su pionero de la tecnología, ¡hasta la próxima!

¡no te vayas! Me gusta , colecciónalo !

Está bien, puedes irte.

Supongo que te gusta

Origin blog.csdn.net/zhenghongzhi6/article/details/111474513
Recomendado
Clasificación