O que é ?[]? sintaxe em c #?

85

Enquanto estudava o delegado, que na verdade é uma classe abstrata Delegate.cs, vi o seguinte método no qual não entendo

  • Por que o valor de retorno usa, ?embora já seja um tipo de referência ( classe )
  • ?[]? significado no parâmetro

Você poderia explicar?

public static Delegate? Combine(params Delegate?[]? delegates)
{
    if (delegates == null || delegates.Length == 0)
        return null;

    Delegate? d = delegates[0];
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}
snr - Restabelecer Monica
fonte
23
Não é uma matriz anulável que pode conter valores anuláveis?
Cid
3
c # 8, agora você pode especificar que uma variável de objeto não pode ser nula. Se você virar essa bandeira compilador, você tem que especificar cada variável que está autorizado a ser nulo.
Jeremy Lakeman 29/12/19

Respostas:

66

Explicação passo a passo:

params Delegate?[] delegates - É uma matriz de anulável Delegate

params Delegate?[]? delegates - A matriz inteira pode ser anulável

Como cada parâmetro é do tipo Delegate?e você retorna um índice da Delegate?[]?matriz, faz sentido que o tipo de retorno seja o Delegate?contrário, o compilador retornaria um erro como se você estivesse pesquisando novamente e intde um método que retorne uma string.

Você pode alterar, por exemplo, seu código para retornar um Delegatetipo como este:

public static Delegate Combine(params Delegate?[]? delegates)
{
    Delegate defaulDelegate = // someDelegate here
    if (delegates == null || delegates.Length == 0)
        return defaulDelegate;

    Delegate d = delegates[0] ?? defaulDelegate;
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}
Athanasios Kataras
fonte
4
e a minha primeira pergunta? Por que o valor de retorno usa ?embora já seja do tipo de referência (classe)
snr - Reinstate Monica
2
Porque você está retornando d, que é Delegado? tipo. E também nulo ou Delegado dependendo dos parâmetros
Athanasios Kataras
2
@snr O valor de retorno usa ?exatamente o mesmo motivo pelo qual o valor do argumento usa o ?. Se você entende o último, entende automaticamente o primeiro. Como o método pode retornar nulo , como o parâmetro pode aceitar nulo . Ou seja, ambos podem conter nulo .
GSerg
2
Eu toquei. Acabei de responder como a segunda parte do exemplo. Since each parameter is of the type Delegate? and you return an index of the Delegate?[]? array, then it makes sense that the return type is Delegate?
Athanasios Kataras
2
@ SNR: Por que o valor de retorno usa? embora já seja do tipo de referência (classe) (1) Pode ser um hábito ou um remanescente de refatoração se estruturas também forem usadas em código semelhante (2) No C # 8, os tipos de referência não podem ser permitidos por serem inerentemente anuláveis ​​(requerendo nulidade explícita ( 3) Foo?possui uma HasValuepropriedade elegante , ao passo que Fooprecisa ser == nullverificada (4) Comunica aos desenvolvedores que leem a assinatura do método que nulos são um resultado possível, esperado e correto. (5) O código parece ser escrito por alguém que gosta muito de nulidade e ser explícito sobre isso.
Flater
25

Tipos de referência anuláveis são novos no C # 8.0, eles não existem antes.

É uma questão de documentação e como os avisos em tempo de compilação são produzidos.

A exceção "objeto não definido para uma instância de um objeto" é silenciosa e comum. Mas essa é uma exceção de tempo de execução, já pode ser parcialmente descoberta em tempo de compilação.

Para regular Delegate dvocê sempre pode ligar

 d.Invoke();

ou seja, você pode codificá-lo, em tempo de compilação nada acontecerá. Pode gerar exceções em tempo de execução.

Enquanto para um novo Delegate? peste Código

 p.Invoke();

produzirá um aviso do compilador. CS8602: Dereference of a possibly null reference a menos que você escreva:

 p?.Invoke();

o que significa, chame apenas se não for nulo.

Portanto, você documenta uma variável que pode conter nulo ou não. Ele gera avisos mais cedo e pode evitar vários testes para nulo. O mesmo que você tem para int e int ?. Você sabe com certeza que um não é nulo - e sabe como converter um para o outro.

Holger
fonte
com a ressalva de que você não sabe ao certo que Delegatenão é nulo. Você apenas finge ter certeza (o que é bom o suficiente na maioria dos casos).
Tim Pohlmann
@ TimPohlmann Assim como int i = null é impossível, também uma string s = null é impossível (em C # 8 com essa alteração de quebra). Portanto, é pouco mais do que fingir alguma coisa. Para compatibilidade com versões anteriores, é rebaixado para um aviso. Se você atualizar o aviso para um erro, terá certeza de que não é nulo.
Holger
4
A última vez que verifiquei NRTs não é 100% à prova de balas. Existem certos casos extremos que o compilador não pode detectar.
Tim Pohlmann
Sim, mas é o mesmo que para avisos de "variável não inicializada" ou "nem todos os caminhos de código retornando um valor". Esses são todos os recursos 100% em tempo de compilação, sem detecção em tempo de execução. Ao contrário de int ?, acho que eles não adicionam memória extra para armazenar informações extras. Então, digamos que seja tão confiável quanto o senso comum. Muito bom.
Holger
11
Se você tiver um, intele nunca poderá ser nulo. Se você tiver um Delegate, pode ser nulo (por várias razões, por exemplo, reflexão). Geralmente, é seguro assumir que Delegate(no C # 8 com NRT ativado) não é nulo, mas você nunca sabe ao certo (onde intsabemos com certeza).
Tim Pohlmann
5

No C # 8, deve-se marcar explicitamente os tipos de referência como nulos.

Por padrão, esses tipos não são capazes de conter nulos, um pouco semelhantes aos tipos de valor. Embora isso não mude o funcionamento das coisas, o verificador de tipos exigirá que você faça isso manualmente.

O código fornecido é refatorado para funcionar com o C # 8, mas não se beneficia desse novo recurso.

public static Delegate? Combine(params Delegate?[]? delegates)
{
    // ...[]? delegates - is not null-safe, so check for null and emptiness
    if (delegates == null || delegates.Length == 0)
        return null;

    // Delegate? d - is not null-safe too
    Delegate? d = delegates[0];
    for (int i = 1; i < delegates.Length; i++)
        d = Combine(d, delegates[i]);

    return d;
}

Aqui está um exemplo de um código atualizado (não funcionando, apenas uma idéia) aproveitando esse recurso. Ele nos salvou de uma verificação nula e simplificou um pouco esse método.

public static Delegate? Combine(params Delegate[] delegates)
{
    // `...[] delegates` - is null-safe, so just check if array is empty
    if (delegates.Length == 0) return null;

    // `d` - is null-safe too, since we know for sure `delegates` is both not null and not empty
    Delegate d = delegates[0];

    for (int i = 1; i < delegates.Length; i++)
        // then here is a problem if `Combine` returns nullable
        // probably, we can add some null-checks here OR mark `d` as nullable
        d = Combine(d, delegates[i]);

    return d;
}
amankkg
fonte
2
those types are not able to contain null, observe que ele gera um aviso do compilador , não um erro. O código funcionará bem (embora possa gerar NullReferenceExceptions). Isso é feito com a compatibilidade com versões anteriores em mente.
JAD