Corri para isso hoje e não tenho ideia de por que o compilador C # não está gerando um erro.
Int32 x = 1;
if (x == null)
{
Console.WriteLine("What the?");
}
Estou confuso sobre como x poderia ser nulo. Especialmente porque esta atribuição definitivamente lança um erro do compilador:
Int32 x = null;
É possível que x possa se tornar nulo, a Microsoft simplesmente decidiu não colocar essa verificação no compilador ou ela foi perdida completamente?
Atualização: depois de mexer no código para escrever este artigo, de repente o compilador apareceu com um aviso de que a expressão nunca seria verdadeira. Agora estou realmente perdido. Coloquei o objeto em uma classe e agora o aviso foi embora, mas deixei a questão, um tipo de valor pode acabar sendo nulo.
public class Test
{
public DateTime ADate = DateTime.Now;
public Test ()
{
Test test = new Test();
if (test.ADate == null)
{
Console.WriteLine("What the?");
}
}
}
if (1 == 2)
. Não é função do compilador realizar a análise do caminho do código; é para isso que servem as ferramentas de análise estática e os testes de unidade.int
, o compilador gera avisos legais. Para os tipos simples, o==
operador é definido pela especificação da linguagem C #. Para outras estruturas (não do tipo simples), o compilador se esquece de emitir um aviso. Consulte o aviso do compilador errado ao comparar struct com nulo para obter detalhes Para estruturas que não são tipos simples, o==
operador deve ser sobrecarregado por umopeartor ==
método que é membro da estrutura (caso contrário, não==
é permitido).Respostas:
Isso é legal porque a resolução de sobrecarga do operador tem um único melhor operador para escolher. Existe um operador == que leva dois ints anuláveis. O int local é conversível em um int anulável. O literal nulo é conversível em um int anulável. Portanto, este é um uso legal do operador == e sempre resultará em falso.
Da mesma forma, também permitimos que você diga "if (x == 12.6)", que também será sempre falso. O int local é conversível em duplo, o literal é conversível em duplo e, obviamente, eles nunca serão iguais.
fonte
static bool operator == (SomeID a, String b)
e marcá- lo comObsolete
? Se o segundo operando for um literal sem tiponull
, seria uma combinação melhor do que qualquer forma que exigisse o uso de operadores elevados, mas se for umSomeID?
que fosse igualnull
, o operador suspenso venceria.Não é um erro, pois há uma
int?
conversão ( ); ele gera um aviso no exemplo dado:Se você verificar o IL, verá que está completamente remove o branch inacessível - ele não existe em uma versão de lançamento.
Observe, entretanto, que ele não gera esse aviso para estruturas personalizadas com operadores de igualdade. Costumava no 2.0, mas não no compilador 3.0. O código ainda é removido (para que saiba que o código está inacessível), mas nenhum aviso é gerado:
using System; struct MyValue { private readonly int value; public MyValue(int value) { this.value = value; } public static bool operator ==(MyValue x, MyValue y) { return x.value == y.value; } public static bool operator !=(MyValue x, MyValue y) { return x.value != y.value; } } class Program { static void Main() { int i = 1; MyValue v = new MyValue(1); if (i == null) { Console.WriteLine("a"); } // warning if (v == null) { Console.WriteLine("a"); } // no warning } }
Com o IL (para
Main
) - observe que tudo, excetoMyValue(1)
(que pode ter efeitos colaterais) foi removido:.method private hidebysig static void Main() cil managed { .entrypoint .maxstack 2 .locals init ( [0] int32 i, [1] valuetype MyValue v) L_0000: ldc.i4.1 L_0001: stloc.0 L_0002: ldloca.s v L_0004: ldc.i4.1 L_0005: call instance void MyValue::.ctor(int32) L_000a: ret }
isto é basicamente:
private static void Main() { MyValue v = new MyValue(1); }
fonte
O fato de que uma comparação nunca pode ser verdadeira não significa que seja ilegal. No entanto, não, um tipo de valor sempre pode ser
null
.fonte
null
. Considereint?
, que é o açúcar sintático paraNullable<Int32>
, que é um tipo de valor. Uma variável de tipoint?
certamente poderia ser igual anull
.==
operador. É importante observar que a instância não é realmente nula.Não,
Int32 x
nunca vai se tornarnull
."Por que uma comparação de um tipo de valor com nulo é um aviso?" artigo irá ajudá-lo.
fonte
Um tipo de valor não pode ser
null
, embora possa ser igual anull
(considereNullable<>
). No seu caso, asint
variáveis enull
são implicitamente convertidasNullable<Int32>
e comparadas.fonte
Suspeito que seu teste específico está apenas sendo otimizado pelo compilador quando ele gera o IL, já que o teste nunca será falso.
Nota lateral: é possível ter um Int32 anulável usando Int32? x em vez disso.
fonte
Eu acho que isso ocorre porque "==" é um açúcar de sintaxe que realmente representa a chamada para o
System.Object.Equals
método que aceitaSystem.Object
parâmetro. Nulo pela especificação ECMA é um tipo especial do qual é derivadoSystem.Object
.É por isso que há apenas um aviso.
fonte
[EDITADO: transformou avisos em erros e tornou os operadores explícitos sobre anuláveis em vez de hackear a string.]
De acordo com a sugestão inteligente de @supercat em um comentário acima, as seguintes sobrecargas de operador permitem gerar um erro sobre as comparações do seu tipo de valor personalizado com nulo.
Ao implementar operadores que se comparam a versões anuláveis de seu tipo, o uso de nulo em uma comparação corresponde à versão anulável do operador, o que permite gerar o erro por meio do atributo Obsolete.
Até que a Microsoft nos devolva nosso aviso do compilador, continuarei com esta solução alternativa, obrigado @supercat!
public struct Foo { private readonly int x; public Foo(int x) { this.x = x; } public override string ToString() { return string.Format("Foo {{x={0}}}", x); } public override int GetHashCode() { return x.GetHashCode(); } public override bool Equals(Object obj) { return x.Equals(obj); } public static bool operator ==(Foo a, Foo b) { return a.x == b.x; } public static bool operator !=(Foo a, Foo b) { return a.x != b.x; } [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)] public static bool operator ==(Foo a, Foo? b) { return false; } [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)] public static bool operator !=(Foo a, Foo? b) { return true; } [Obsolete("The result of the expression is always 'false' since a value of type 'Foo' is never equal to 'null'", true)] public static bool operator ==(Foo? a, Foo b) { return false; } [Obsolete("The result of the expression is always 'true' since a value of type 'Foo' is never equal to 'null'", true)] public static bool operator !=(Foo? a, Foo b) { return true; } }
fonte
Foo a; Foo? b; ... if (a == b)...
, mesmo que tal comparação seja perfeitamente legítima. A razão pela qual sugeri o "string hack" é que permitiria a comparação acima, mas seria desagradávelif (a == null)
. Em vez de usarstring
, pode-se substituir qualquer tipo de referência diferente deObject
ouValueType
; se desejado, pode-se definir uma classe fictícia com um construtor privado que nunca poderia ser chamado e intitulá-laReferenceThatCanOnlyBeNull
.Acho que a melhor resposta de por que o compilador aceita isso é para classes genéricas. Considere a seguinte classe ...
public class NullTester<T> { public bool IsNull(T value) { return (value == null); } }
Se o compilador não aceitar comparações com os
null
tipos de valor, ele essencialmente quebraria essa classe, tendo uma restrição implícita anexada ao seu parâmetro de tipo (ou seja, ele só funcionaria com tipos não baseados em valor).fonte
O compilador permitirá que você compare qualquer struct que implemente o
==
com nulo. Ele ainda permite que você compare um int com um nulo (no entanto, você receberia um aviso).Mas se você desmontar o código, verá que a comparação está sendo resolvida quando o código é compilado. Então, por exemplo, este código (onde
Foo
está uma implementação de estrutura==
):public static void Main() { Console.WriteLine(new Foo() == new Foo()); Console.WriteLine(new Foo() == null); Console.WriteLine(5 == null); Console.WriteLine(new Foo() != null); }
Gera este IL:
.method public hidebysig static void Main() cil managed { .entrypoint // Code size 45 (0x2d) .maxstack 2 .locals init ([0] valuetype test3.Program/Foo V_0) IL_0000: nop IL_0001: ldloca.s V_0 IL_0003: initobj test3.Program/Foo IL_0009: ldloc.0 IL_000a: ldloca.s V_0 IL_000c: initobj test3.Program/Foo IL_0012: ldloc.0 IL_0013: call bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo, valuetype test3.Program/Foo) IL_0018: call void [mscorlib]System.Console::WriteLine(bool) IL_001d: nop IL_001e: ldc.i4.0 IL_001f: call void [mscorlib]System.Console::WriteLine(bool) IL_0024: nop IL_0025: ldc.i4.1 IL_0026: call void [mscorlib]System.Console::WriteLine(bool) IL_002b: nop IL_002c: ret } // end of method Program::Main
Como você pode ver:
Console.WriteLine(new Foo() == new Foo());
Está traduzido para:
IL_0013: call bool test3.Program/Foo::op_Equality(valuetype test3.Program/Foo, valuetype test3.Program/Foo)
Enquanto que:
Console.WriteLine(new Foo() == null);
É traduzido para falso:
IL_001e: ldc.i4.0
fonte