Em um projeto C # 8 com tipos de referência anuláveis ativados, tenho o seguinte código que acho que deveria me dar um aviso sobre uma possível desreferência nula, mas não:
public class ExampleClassMember
{
public int Value { get; }
}
public struct ExampleStruct
{
public ExampleClassMember Member { get; }
}
public class Program
{
public static void Main(string[] args)
{
var instance = new ExampleStruct();
Console.WriteLine(instance.Member.Value); // expected warning here about possible null dereference
}
}
Quando instance
é inicializado com o construtor padrão, instance.Member
é definido como o valor padrão de ExampleClassMember
, que é null
. Assim, instance.Member.Value
lançará um NullReferenceException
em tempo de execução. Como eu entendo a detecção de nulidade do C # 8, devo receber um aviso do compilador sobre essa possibilidade, mas não o faço; por que é que?
c#
nullable
c#-8.0
nullable-reference-types
DylanSp
fonte
fonte
ExampleStruct
destruct
paraclass
.Nullable
Respostas:
Observe que não há motivo para que haja um aviso na chamada para
Console.WriteLine()
. A propriedade do tipo de referência não é um tipo anulável e, portanto, não é necessário que o compilador avise que pode ser nulo.Você pode argumentar que o compilador deve avisar sobre a referência em
struct
si. Isso me pareceria razoável. Mas isso não acontece. Isso parece ser uma brecha, causada pela inicialização padrão para tipos de valor, ou seja, sempre deve haver um construtor padrão (sem parâmetros), que sempre zera todos os campos (nulos para campos de tipo de referência, zeros para tipos numéricos etc.) )Eu chamo isso de brecha, porque, em teoria, os valores de referência não nulos devem, de fato, ser sempre nulos! Duh. :)
Esta brecha parece ser abordada neste artigo do blog: Apresentando tipos de referência nulos em C #
Em outras palavras, sim, isso é uma brecha, mas não, não é um bug. Os designers de idiomas estão cientes disso, mas optaram por deixar esse cenário de fora dos avisos, porque fazer o contrário seria impraticável, dada a maneira como a
struct
inicialização funciona.Observe que isso também está de acordo com a filosofia mais ampla por trás do recurso. Do mesmo artigo:
Observe também que esse mesmo problema existe com matrizes de tipos de referência nominalmente não nulos (por exemplo
string[]
). Quando você cria a matriz, todos os valores de referência sãonull
e, no entanto, isso é legal e não gera nenhum aviso.Tanta coisa para explicar por que as coisas são do jeito que são. Então a pergunta se torna: o que fazer sobre isso? Isso é muito mais subjetivo, e não acho que haja uma resposta certa ou errada. Dito isto…
Eu pessoalmente trataria meus
struct
tipos caso a caso. Para aqueles em que a intenção é realmente um tipo de referência anulável, eu aplicaria a?
anotação. Caso contrário, eu não faria.Tecnicamente, todo valor de referência em um
struct
deve ser "anulável", ou seja, incluir a?
anotação anulável com o nome do tipo. Mas, como ocorre com muitos recursos semelhantes (como assíncrono / aguardar em C # ouconst
em C ++), isso tem um aspecto "infeccioso", pois você precisará substituir essa anotação posteriormente (com a!
anotação) ou incluir uma verificação nula explícita , ou apenas atribua esse valor a outra variável de tipo de referência anulável.Para mim, isso anula muito a finalidade de habilitar tipos de referência anuláveis. Como esses membros de
struct
tipos exigirão tratamento de caso especial em algum momento, e como a única maneira de lidar com segurança com segurança e ainda poder usar tipos de referência não nulos é colocar verificações nulas em todos os lugares em que você usarstruct
, eu sinto que é uma opção de implementação razoável aceitar que, quando o código inicializastruct
, é responsabilidade do código fazê-lo corretamente e garantir que o membro do tipo de referência não anulável seja realmente inicializado com um valor não nulo.Isso pode ser auxiliado fornecendo um meio "oficial" de inicialização, como um construtor não padrão (por exemplo, um com parâmetros) ou um método de fábrica. Sempre haverá sempre o risco de usar o construtor padrão, ou nenhum construtor (como nas alocações de matriz), mas fornecendo um meio conveniente para inicializar o
struct
correto, isso incentivará o código que o usa para evitar referências nulas em variáveis anuláveis.Dito isso, se o que você deseja é 100% de segurança em relação aos tipos de referência anuláveis, então claramente a abordagem correta para esse objetivo específico é sempre anotar todos os membros do tipo de referência em um
struct
com?
. Isso significa todos os campos e propriedades implementadas automaticamente, juntamente com qualquer método ou método de obtenção de propriedades que retorne diretamente esses valores ou o produto desses valores. Em seguida, o código de consumo precisará incluir verificações nulas ou o operador que perdoa nulas em todos os pontos em que esses valores são copiados em variáveis não nulas.fonte
À luz da excelente resposta de @ peter-duniho, parece que, a partir de outubro de 2019, é melhor marcar todos os membros que não são do tipo valor como uma referência nula.
fonte