Quais são os novos recursos do C#8 com suporte no Unity2020.2?

A Academia Hongliu o deixará alguns passos mais rápido. Olá, sou Zheng Hongzhi, seu desbravador de tecnologia, pode me chamar de Dazhi.

O C# 8.0 adiciona os seguintes recursos e aprimoramentos à linguagem C#:

  • Membros somente leitura
  • método de interface padrão
  • Melhorias na correspondência de padrões:
    • mudar expressão
    • modo de atributo
    • padrão de tupla
    • modo de localização
  • Usando instrução
  • função local estática
  • Estrutura ref descartável
  • tipo de referência anulável
  • fluxo assíncrono
  • liberável assíncrono
  • Índices e intervalos
  • Atribuição de coalescência nula
  • tipo construído não gerenciado
  • Stackalloc em expressões aninhadas
  • Aprimoramentos para interpolar strings literais

Membros somente leitura

readonlyO modificador pode ser aplicado a membros de uma estrutura. Indica que o membro não modificará o estado. Isso readonlyé structmais granular do que aplicar o modificador à declaração. Considere a seguinte estrutura mutável:

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 a maioria das estruturas, ToString()os métodos não modificam o estado. Isso pode ser indicado adicionando readonlyo modificador à declaração do :ToString()

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

A alteração acima gera um aviso do compilador porque ToStringacessa uma propriedadereadonly que não 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'

O compilador emitirá um aviso quando for necessário criar uma cópia defensiva. DistanceOs atributos não mudam de estado, então este aviso pode ser corrigido adicionando readonlyo modificador à declaração:

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

Observe que readonlyos modificadores são necessários para propriedades somente leitura. O compilador assumirá que getum acessador pode modificar o estado; deve ser explicitamente declarado readonly. Uma exceção são as propriedades auto-implementadas; o compilador trata todos os getters auto-implementados como tal , então não há necessidade de adicionar modificadores aquireadonly .XYreadonly

O compilador impõe a regra de que readonlyos membros não modificam o estado. Os seguintes métodos não serão compilados a menos que readonlyo modificador :

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

Esse recurso permite que a intenção do design seja especificada para que o compilador possa aplicar essa intenção e otimizar com base nessa intenção.

método de interface padrão

Agora você pode adicionar membros à interface e fornecer implementações para esses membros. Esse recurso de linguagem permite que os autores de API adicionem métodos a versões futuras de uma interface sem interromper a compatibilidade de fonte ou binária com a implementação atual da interface. As implementações existentes herdam a implementação padrão. Esse recurso permite que o C# interopere com APIs voltadas para Android ou Swift, que dão suporte a funcionalidades semelhantes. Os métodos de interface padrão também oferecem suporte a cenários semelhantes ao recurso de linguagem "traços".

Use mais padrões em mais lugares

A correspondência de padrões fornece ferramentas para fornecer recursos relacionados à forma em tipos de dados relacionados, mas diferentes. C# is7.0 switchapresenta a sintaxe para padrões de tipo e padrões constantes por meio do uso de expressões e instruções. Essas funções representam uma tentativa inicial de um paradigma de programação que suporta a separação de dados e funcionalidade. Ferramentas de linguagem adicionais são necessárias à medida que o setor se move para mais microsserviços e outras arquiteturas baseadas em nuvem.

O C# 8.0 expande esse vocabulário para que mais expressões padrão possam ser usadas em mais lugares no código. Considere o uso dessas funções quando os dados e a funcionalidade estiverem separados. Considere o uso de correspondência de padrões quando um algoritmo depende de fatos diferentes do tipo de tempo de execução do objeto. Essas técnicas fornecem outra maneira de expressar projetos.

Além de poder usar novos padrões em novos locais, o C# 8.0 adiciona "padrões recursivos". O resultado de qualquer expressão de padrão é uma expressão. Um padrão recursivo é apenas uma expressão de padrão aplicada à saída de outra expressão de padrão.

mudar expressão

Normalmente, uma instrução switch produz um valor em cada um de seus caseblocos . Com as expressões Switch, uma sintaxe de expressão mais concisa está disponível. Existem apenas algumas repetições casedas palavras- breakchave e e chaves. Tome a seguinte enumeração que lista as cores do arco-íris como exemplo:

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

Se seu aplicativo definir um tipo construído a partir de R, Ge Bcomponents RGBColor, você poderá usar o método a seguir com uma expressão switch para Rainbowconverter em um valor RGB:

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

Aqui estão várias melhorias de sintaxe:

  • As variáveis ​​precedem switcha palavra-chave . A ordem diferente torna fácil distinguir visualmente as expressões switch das instruções switch.
  • caseSubstitua os elementos e :por =>. É mais limpo e intuitivo.
  • defaultSubstitua instâncias por _descartes.
  • O corpo é uma expressão, não uma afirmação.

Compare isso com o código equivalente usando a switchinstrução :

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

Com padrões de atributos, é possível combinar atributos do objeto que está sendo inspecionado. Considere o exemplo de um site de comércio eletrônico que deve calcular o imposto sobre vendas com base no endereço do comprador. Este cálculo não é responsabilidade central da Addressclasse . Ele muda com o tempo, provavelmente com mais frequência do que as mudanças de formato de endereço. O valor do imposto sobre vendas depende Statedos atributos . O método a seguir calcula o imposto sobre vendas a partir do endereço e do preço usando o padrão de atributo:

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

A correspondência de padrões cria uma sintaxe concisa para expressar esse algoritmo.

padrão de tupla

Alguns algoritmos dependem de várias entradas. Use o padrão de tupla para alternar com base em vários valores representados como uma tupla. O código a seguir mostra a expressão de alternância para o jogo "pedra, papel, tesoura":

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

A mensagem indica o vencedor. Um descarte significa três combinações de empate (jogo pedra-papel-tesoura) ou outra entrada de texto.

modo de localização

Alguns tipos contêm Deconstructum método que desestrutura suas propriedades em variáveis ​​discretas. Se você tiver acesso Deconstructao método , poderá usar o padrão de localização para examinar as propriedades do objeto e usar essas propriedades no padrão. Considere a seguinte Pointclasse , que contém métodos para criarX variáveis ​​discretas para e :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);
}

Além disso, considere as seguintes enumerações que representam as várias posições dos quadrantes:

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

O método abaixo usa o padrão posicional para extrair xos yvalores e. Em seguida, ele usa whena cláusula para determinar o ponto 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
};

O padrão de descarte na troca anterior corresponde quando xou é 0 (mas não ambos). yUma expressão Switch deve produzir um valor ou gerar uma exceção. Se nenhuma dessas condições corresponder, a expressão switch lançará uma exceção. O compilador gerará um aviso se nem todos os casos possíveis forem cobertos na expressão switch.

usando declaração

Uma declaração using é uma declaração de variável precedida pela usingpalavra-chave . Ele instrui o compilador que as variáveis ​​declaradas devem ser processadas no final do escopo delimitador. Tome o seguinte código para escrever um arquivo de texto como exemplo:

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
}

No exemplo anterior, o arquivo é processado quando o colchete de fechamento do método é atingido. Este é o fim do escopo filedeclarado . O código anterior é equivalente ao código a seguir usando a instrução using clássica:

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
}

No exemplo anterior, o arquivo é processado quando o colchete de fechamento associado à usinginstrução .

Em ambos os casos, o compilador irá gerar uma chamada Dispose()para . O compilador gerará um erro se a expressão na usinginstrução não estiver disponível.

função local estática

staticO modificador agora pode ser adicionado a funções locais para garantir que as funções locais não capturem (referenciam) nenhuma variável do escopo delimitador. Isso gera CS8421, "Uma função local estática não pode conter uma referência a."

Considere o seguinte código. Funções locais LocalFunctionacessam Mvariáveis ​​declaradas no escopo delimitador (método) y. Portanto, não pode ser declarado com statico modificador LocalFunction:

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

    void LocalFunction() => y = 0;
}

O código abaixo contém uma função local estática. Pode ser estático, pois não acessa nenhuma variável no escopo envolvente:

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

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

Estrutura ref descartável

Um declarado com refo modificador structnão pode implementar nenhuma interface e, portanto, não pode implementar IDisposable, portanto, para poder descartá- ref structlo, deve ter um void Dispose()método . Essa funcionalidade também se aplica a readonly ref structdeclarações .

tipo de referência anulável

No contexto de uma anotação anulável, qualquer variável de um tipo de referência é considerada um tipo de referência não anulável. Para indicar que uma variável pode ser nula, o nome do tipo deve ser anexado ?para declarar a variável como um tipo de referência anulável.

Para tipos de referência não anuláveis, o compilador usa análise de fluxo para garantir que as variáveis ​​locais sejam inicializadas com valores não nulos no momento da declaração. Os campos devem ser inicializados durante a construção. O compilador gerará um aviso se a variável não for definida chamando qualquer um dos construtores disponíveis ou por uma expressão de inicialização. Além disso, você não pode atribuir um valor anulável a um tipo de referência não anulável.

Os tipos de referência anuláveis ​​não são verificados para garantir que não recebam um valor nulo ou inicializado como nulo. No entanto, o compilador usa análise de fluxo para garantir que qualquer variável de um tipo de referência anulável seja verificada quanto à nulidade antes de ser acessada ou atribuída a um tipo de referência não anulável.

fluxo assíncrono

A partir do C# 8.0, os fluxos podem ser criados e consumidos de forma assíncrona. Os métodos que retornam um fluxo assíncrono têm três propriedades:

  1. É declarado com asynco modificador .
  2. Ele retornará IAsyncEnumerable.
  3. Este método contém yield returninstruções .

Usar fluxos assíncronos foreachrequer prefixar awaita palavra-chave ao enumerar elementos de fluxo. Adicionar awaita palavra-chave requer métodos que enumeram fluxos assíncronos para asyncdeclarar com o modificador e retornar os tipos permitidos pelo asyncmétodo . Normalmente, isso significa retornar Taskou Task<TResult>. Também pode ser ValueTaskou ValueTask<TResult>. Um método pode consumir um fluxo assíncrono ou produzir um fluxo assíncrono, o que significa que retornará IAsyncEnumerable<T>. O código a seguir gera uma sequência de 0 a 19, aguardando 100 milissegundos entre a geração 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;
    }
}

As sequências podem ser enumeradas usando await foreacha instrução :

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

Por padrão, os elementos de fluxo são processados ​​no contexto capturado. Se você deseja desabilitar a captura de contexto, use o método de extensão TaskAsyncEnumerableExtensions.ConfigureAwait.

## liberável assíncrono

A partir do C# 8.0, a linguagem dá suporte a tipos descartáveis ​​assíncronos que implementam a interface System.IAsyncDisposable. Objetos descartáveis ​​assíncronos podem ser manipulados usando await usinga instrução .

Índices e intervalos

Índices e intervalos fornecem sintaxe concisa para acessar elementos individuais ou intervalos em uma sequência.

Este suporte de idioma conta com dois novos tipos e dois novos operadores:

  • System.IndexRepresenta um índice de sequência.
  • O índice do ^operador especifica um índice relativo ao final da sequência.
  • System.RangeRepresenta um subintervalo de uma sequência.
  • Operador de intervalo .., usado para especificar o início e o fim de um intervalo, assim como os operandos.

Vamos começar com as regras de indexação. Considere matrizes sequence. 0Índice é sequence[0]o mesmo que . ^0Índice é sequence[sequence.Length]o mesmo que . Observe que sequence[^0]nenhuma exceção é lançada, assim sequence[sequence.Length]como . Para qualquer número n, o índice ^né sequence.Length - no mesmo que .

intervalo especifica o início e o fim do intervalo. Inclua o início do intervalo, mas não o final do intervalo, o que significa que o intervalo inclui o início, mas não o fim. Alcance [0..^0]significa todo o alcance, assim como [0..sequence.Length]significa todo o alcance.

Dê uma olhada em alguns exemplos abaixo. Considere o seguinte array, anotado com seus índices ascendentes e 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

A última palavra pode ser recuperada usando ^1o índice :


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

O código a seguir cria um subintervalo que contém as palavras "quick", "brown" e "fox". Inclui words[1]a words[3]. O elemento words[4]não está no intervalo.

var quickBrownFox = words[1..4];

O código a seguir cria um subintervalo usando "lazy" e "dog". Inclui words[^2]e words[^1]. Índice final words[^0]não incluído:

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

O exemplo a seguir cria intervalos abertos para início e/ou fim:

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

Além disso, os escopos podem ser declarados como variáveis:

Range phrase = 1..4;

O intervalo pode então ser usado dentro [dos caracteres e :]

var text = words[phrase];

Não apenas arrays suportam indexação e intervalos. Você também pode usar índices e intervalos com strings, Spans ou ReadOnlySpans.

Atribuição de coalescência nula

O C# 8.0 introduziu o operador de atribuição de coalescência nula ??=. O operador pode ser usado para atribuir o valor de seu operando direito ao operando esquerdo somente nullse .??=

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 construído não gerenciado

No C# 7.3 e anteriores, um tipo construído (um tipo que contém pelo menos um parâmetro de tipo) não pode ser um tipo não gerenciado. A partir do C# 8.0, se um tipo de valor construído contiver apenas campos de um tipo não gerenciado, o tipo será não gerenciado.

Por exemplo, suponha que o Coords<T>tipo tenha a seguinte definição

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

Coords<int>O tipo é um tipo não gerenciado em C# 8.0 e posterior. Como em qualquer tipo não gerenciado, é possível criar ponteiros para variáveis ​​desse tipo ou alocar blocos de memória na pilha para instâncias desse 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 em expressões aninhadas

A partir do C# 8.0, se o resultado de uma expressão stackalloc for do tipo System.Span ou System.ReadOnlySpan, você poderá usar stackalloca expressão :

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

Aprimoramentos para interpolar strings literais

A ordem dos tokens $e @pode ser arbitrária: $@"..."e @$"..."são strings textual interpoladas válidas. Nas versões anteriores do C#, $a tag tinha que aparecer antes @da tag .

[Aprendizagem estendida] Responda na conta oficial da Hongliu Academyruntime para obter todos os artigos desta série.


Eu sou Dazhi (vx: zhz11235), seu desbravador de tecnologia, até a próxima!

não vá! Gostou , colecione !

OK, voce pode ir.

おすすめ

転載: blog.csdn.net/zhenghongzhi6/article/details/111474513