Tipos de referência anuláveis em C #Nullable reference types in C#

O objetivo desse recurso é:The goal of this feature is to:

  • Permitir que os desenvolvedores expressem se uma variável, um parâmetro ou um resultado de um tipo de referência deve ser nulo ou não.Allow developers to express whether a variable, parameter or result of a reference type is intended to be null or not.
  • Forneça avisos quando tais variáveis, parâmetros e resultados não forem usados de acordo com essa intenção.Provide warnings when such variables, parameters and results are not used according to that intent.

Expressão de intençãoExpression of intent

O idioma já contém a T? sintaxe para tipos de valor.The language already contains the T? syntax for value types. É simples estender essa sintaxe para tipos de referência.It is straightforward to extend this syntax to reference types.

Supõe-se que a intenção de um tipo de referência não adornado T seja para que ele seja não nulo.It is assumed that the intent of an unadorned reference type T is for it to be non-null.

Verificação de referências anuláveisChecking of nullable references

Uma análise de fluxo acompanha variáveis de referência anuláveis.A flow analysis tracks nullable reference variables. Quando a análise considera que ela não seria nula (por exemplo, após uma verificação ou uma atribuição), seu valor será considerado uma referência não nula.Where the analysis deems that they would not be null (e.g. after a check or an assignment), their value will be considered a non-null reference.

Uma referência anulável também pode ser tratada explicitamente como não-nula com o operador de sufixo x! (o operador "damnit"), para quando a análise de fluxo não puder estabelecer uma situação não nula que o desenvolvedor saiba.A nullable reference can also explicitly be treated as non-null with the postfix x! operator (the "damnit" operator), for when flow analysis cannot establish a non-null situation that the developer knows is there.

Caso contrário, um aviso será fornecido se uma referência anulável for desreferenciada ou for convertida em um tipo não nulo.Otherwise, a warning is given if a nullable reference is dereferenced, or is converted to a non-null type.

Um aviso é fornecido ao converter de S[] para T?[] e de S?[] para T[] .A warning is given when converting from S[] to T?[] and from S?[] to T[].

Um aviso é fornecido ao converter de C<S> para C<T?> , exceto quando o parâmetro de tipo é covariant ( out ) e ao converter de C<S?> para C<T> , exceto quando o parâmetro de tipo é contravariant ( in ).A warning is given when converting from C<S> to C<T?> except when the type parameter is covariant (out), and when converting from C<S?> to C<T> except when the type parameter is contravariant (in).

Um aviso será fornecido em C<T?> se o parâmetro de tipo tiver restrições não nulas.A warning is given on C<T?> if the type parameter has non-null constraints.

Verificando referências não nulasChecking of non-null references

Um aviso será fornecido se um literal nulo for atribuído a uma variável não nula ou passado como um parâmetro não nulo.A warning is given if a null literal is assigned to a non-null variable or passed as a non-null parameter.

Um aviso também será fornecido se um construtor não inicializar explicitamente os campos de referência não nulos.A warning is also given if a constructor does not explicitly initialize non-null reference fields.

Não podemos rastrear adequadamente que todos os elementos de uma matriz de referências não nulas são inicializados.We cannot adequately track that all elements of an array of non-null references are initialized. No entanto, poderíamos emitir um aviso se nenhum elemento de uma matriz recém-criada for atribuído antes de a matriz ser lida ou passada.However, we could issue a warning if no element of a newly created array is assigned to before the array is read from or passed on. Isso pode lidar com o caso comum sem muito ruído.That might handle the common case without being too noisy.

Precisamos decidir se default(T) o gera um aviso ou é simplesmente tratado como sendo do tipo T? .We need to decide whether default(T) generates a warning, or is simply treated as being of the type T?.

Representação de metadadosMetadata representation

Adornos de nulidade devem ser representados em metadados como atributos.Nullability adornments should be represented in metadata as attributes. Isso significa que os compiladores de nível inferior irão ignorá-los.This means that downlevel compilers will ignore them.

Precisamos decidir se apenas anotações anuláveis estão incluídas, ou também há alguma indicação de se não nulo foi "ligado" no assembly.We need to decide if only nullable annotations are included, or there's also some indication of whether non-null was "on" in the assembly.

GenéricosGenerics

Se um parâmetro de tipo T tiver restrições não anuláveis, ele será tratado como não anulável dentro de seu escopo.If a type parameter T has non-nullable constraints, it is treated as non-nullable within its scope.

Se um parâmetro de tipo for irrestrito ou tiver apenas restrições anuláveis, a situação será um pouco mais complexa: isso significa que o argumento de tipo correspondente pode ser anulável ou não anulável.If a type parameter is unconstrained or has only nullable constraints, the situation is a little more complex: this means that the corresponding type argument could be either nullable or non-nullable. A coisa segura a fazer nessa situação é tratar o parâmetro de tipo como anulável e não anulável, fornecendo avisos quando um deles for violado.The safe thing to do in that situation is to treat the type parameter as both nullable and non-nullable, giving warnings when either is violated.

Vale a pena considerar se as restrições de referência anuláveis explícitas devem ser permitidas.It is worth considering whether explicit nullable reference constraints should be allowed. No entanto, observe que não podemos evitar ter tipos de referência anuláveis implicitamente restrições em determinados casos (restrições herdadas).Note, however, that we cannot avoid having nullable reference types implicitly be constraints in certain cases (inherited constraints).

A class restrição não é nula.The class constraint is non-null. Podemos considerar se class? deve ser uma restrição anulável válida, indicando "tipo de referência anulável".We can consider whether class? should be a valid nullable constraint denoting "nullable reference type".

Inferência de tiposType inference

Na inferência de tipos, se um tipo de contribuição for um tipo de referência anulável, o tipo resultante deverá ser anulável.In type inference, if a contributing type is a nullable reference type, the resulting type should be nullable. Em outras palavras, a nulidade é propagada.In other words, nullness is propagated.

Devemos considerar se o null literal como uma expressão participante deve contribuir com a nulidade.We should consider whether the null literal as a participating expression should contribute nullness. Não hoje: para tipos de valor, ele leva a um erro, ao passo que, para tipos de referência, o NULL converte com êxito o tipo Plain.It doesn't today: for value types it leads to an error, whereas for reference types the null successfully converts to the plain type.

string? n = "world";
var x = b ? "Hello" : n; // string?
var y = b ? "Hello" : null; // string? or error
var z = b ? 7 : null; // Error today, could be int?

Diretrizes de proteção nulaNull guard guidance

Como um recurso, os tipos de referência anuláveis permitem aos desenvolvedores expressar suas intenções e fornecem avisos por meio da análise de fluxo se essa intenção for contraditória.As a feature, nullable reference types allow developers to express their intent, and provide warnings through flow analysis if that intent is contradicted. Há uma pergunta comum sobre se as proteções nulas devem ou não ser necessárias.There is a common question as to whether or not null guards are necessary.

Exemplo de proteção nulaExample of null guard

public void DoWork(Worker worker)
{
    // Guard against worker being null
    if (worker is null)
    {
        throw new ArgumentNullException(nameof(worker));
    }

    // Otherwise use worker argument
}

No exemplo anterior, a DoWork função aceita um Worker e protege contra ele potencialmente null .In the previous example, the DoWork function accepts a Worker and guards against it potentially being null. Se o worker argumento for null , a DoWork função irá throw .If the worker argument is null, the DoWork function will throw. Com tipos de referência anuláveis, o código no exemplo anterior faz a intenção de que o Worker parâmetro não seria null .With nullable reference types, the code in the previous example makes the intent that the Worker parameter would not be null. Se a DoWork função era uma API pública, como um pacote NuGet ou uma biblioteca compartilhada, como orientação, você deve deixar proteções nulas em vigor.If the DoWork function was a public API, such as a NuGet package or a shared library - as guidance you should leave null guards in place. Como uma API pública, a única garantia de que um chamador não null está passando é a proteção contra ele.As a public API, the only guarantee that a caller isn't passing null is to guard against it.

Intenção expressaExpress intent

Um uso mais atraente do exemplo anterior é expressar que o Worker parâmetro poderia ser null , tornando a proteção nula mais apropriada.A more compelling use of the previous example is to express that the Worker parameter could be null, thus making the null guard more appropriate. Se você remover a proteção nula no exemplo a seguir, o compilador avisará que você pode estar desreferenciando NULL.If you remove the null guard in the following example, the compiler warns that you may be dereferencing null. Independentemente de, ambas as proteções nulas ainda são válidas.Regardless, both null guards are still valid.

public void DoWork(Worker? worker)
{
    // Guard against worker being null
    if (worker is null)
    {
        throw new ArgumentNullException(nameof(worker));
    }

    // Otherwise use worker argument
}

Para APIs não públicas, como o código-fonte totalmente no controle de uma equipe de desenvolvedor ou de desenvolvimento, os tipos de referência anuláveis podem permitir a remoção segura de proteções nulas, nas quais os desenvolvedores podem garantir que isso não seja necessário.For non-public APIs, such as source code entirely in control by a developer or dev team - the nullable reference types could allow for the safe removal of null guards where the developers can guarantee it is not necessary. O recurso pode ajudar com avisos, mas não pode garantir que na execução de código de tempo de execução possa resultar em um NullReferenceException .The feature can help with warnings, but it cannot guarantee that at runtime code execution could result in a NullReferenceException.

Alterações de quebraBreaking changes

Avisos não nulos são uma alteração significativa de quebra no código existente e devem ser acompanhados por um mecanismo de aceitação.Non-null warnings are an obvious breaking change on existing code, and should be accompanied with an opt-in mechanism.

Menos óbvio, os avisos de tipos anuláveis (conforme descrito acima) são uma alteração significativa no código existente em determinados cenários em que a nulidade é implícita:Less obviously, warnings from nullable types (as described above) are a breaking change on existing code in certain scenarios where the nullability is implicit:

  • Os parâmetros de tipo irrestrito serão tratados como anuláveis implicitamente, portanto, atribuí-los object ou acessar, por exemplo, ToString resultarão em avisos.Unconstrained type parameters will be treated as implicitly nullable, so assigning them to object or accessing e.g. ToString will yield warnings.
  • se a inferência de tipos inferir a nulidade de null expressões, o código existente, às vezes, resultará em tipos anuláveis em vez de não anuláveis, o que pode levar a novos avisos.if type inference infers nullness from null expressions, then existing code will sometimes yield nullable rather than non-nullable types, which can lead to new warnings.

Portanto, os avisos anuláveis também precisam ser opcionaisSo nullable warnings also need to be optional

Por fim, adicionar anotações a uma API existente será uma alteração significativa para os usuários que optaram por avisos, quando eles atualizarem a biblioteca.Finally, adding annotations to an existing API will be a breaking change to users who have opted in to warnings, when they upgrade the library. Isso também merece a capacidade de aceitar ou sair. "Desejo as correções de bugs, mas não estou pronto para lidar com suas novas anotações"This, too, merits the ability to opt in or out. "I want the bug fixes, but I am not ready to deal with their new annotations"

Em resumo, você precisa ser capaz de aceitar/sair de:In summary, you need to be able to opt in/out of:

  • Avisos anuláveisNullable warnings
  • Avisos não nulosNon-null warnings
  • Avisos de anotações em outros arquivosWarnings from annotations in other files

A granularidade da aceitação sugere um modelo como o analisador, em que faixas de código pode aceitar e cancelar com pragmas e níveis de severidade podem ser escolhidos pelo usuário.The granularity of the opt-in suggests an analyzer-like model, where swaths of code can opt in and out with pragmas and severity levels can be chosen by the user. Além disso, as opções por biblioteca ("ignorar as anotações de JSON.NET até que eu esteja pronto para lidar com a saída") podem ser expressas em código como atributos.Additionally, per-library options ("ignore the annotations from JSON.NET until I'm ready to deal with the fall out") may be expressible in code as attributes.

O design da experiência de aceitação/transição é crucial para o sucesso e a utilidade desse recurso.The design of the opt-in/transition experience is crucial to the success and usefulness of this feature. Precisamos garantir que:We need to make sure that:

  • Os usuários podem adotar a verificação de nulidade gradualmente, como desejamUsers can adopt nullability checking gradually as they want to
  • Os autores de biblioteca podem adicionar anotações de nulidade sem medo de quebrar clientesLibrary authors can add nullability annotations without fear of breaking customers
  • Apesar desses, não há uma noção de "pesadelo de configuração"Despite these, there is not a sense of "configuration nightmare"

AjustesTweaks

Poderíamos considerar não usar as ? anotações em locais, mas apenas observando se elas são usadas de acordo com o que é atribuído a elas.We could consider not using the ? annotations on locals, but just observing whether they are used in accordance with what gets assigned to them. Eu não prefiro isso; Acho que devemos permitir que as pessoas expressem suas intenções.I don't favor this; I think we should uniformly let people express their intent.

Poderíamos considerar uma abreviação T! x de parâmetros, que gera automaticamente uma verificação nula em tempo de execução.We could consider a shorthand T! x on parameters, that auto-generates a runtime null check.

Determinados padrões em tipos genéricos, como FirstOrDefault ou TryGet , têm um comportamento um pouco estranho com argumentos de tipo não anuláveis, pois eles implicitamente geram valores padrão em determinadas situações.Certain patterns on generic types, such as FirstOrDefault or TryGet, have slightly weird behavior with non-nullable type arguments, because they explicitly yield default values in certain situations. Poderíamos tentar nuancer o sistema de tipos para acomodá-lo melhor.We could try to nuance the type system to accommodate these better. Por exemplo, poderíamos permitir ? em parâmetros de tipo irrestrito, embora o argumento de tipo já possa ser anulável.For instance, we could allow ? on unconstrained type parameters, even though the type argument could already be nullable. Acho que vale a pena, e isso leva à estranhaidade relacionada à interação com tipos de valor anulável.I doubt that it is worth it, and it leads to weirdness related to interaction with nullable value types.

Tipos de valor anuláveisNullable value types

Poderíamos considerar a adoção de algumas das semânticas acima para tipos de valor anuláveis também.We could consider adopting some of the above semantics for nullable value types as well.

Já mencionamos inferência de tipos, onde poderíamos inferir int? (7, null) , em vez de apenas dar um erro.We already mentioned type inference, where we could infer int? from (7, null), instead of just giving an error.

Outra oportunidade é aplicar a análise de fluxo a tipos de valor anuláveis.Another opportunity is to apply the flow analysis to nullable value types. Quando eles são considerados não nulos, podemos realmente permitir o uso como o tipo não anulável de determinadas maneiras (por exemplo, acesso de membro).When they are deemed non-null, we could actually allow using as the non-nullable type in certain ways (e.g. member access). Precisamos apenas ter cuidado para que as coisas que você possa fazer em um tipo de valor anulável sejam preferenciais, para fins de compatibilidade de volta.We just have to be careful that the things that you can already do on a nullable value type will be preferred, for back compat reasons.