'é' versus tentar lançar com verificação nula

106

Percebi que Resharper sugere que eu mude este:

if (myObj.myProp is MyType)
{
   ...
}

nisso:

var myObjRef = myObj.myProp as MyType;
if (myObjRef != null)
{
   ...
}

Por que sugeriria essa mudança? Estou acostumado a Resharper sugerindo mudanças de otimização e mudanças de redução de código, mas parece que ele quer pegar minha única instrução e transformá-la em duas linhas.

De acordo com o MSDN :

Uma expressão is avaliada como verdadeira se as seguintes condições forem atendidas:

expressão não é nula. a expressão pode ser convertida em tipo . Ou seja, uma expressão de conversão do formulário (type)(expression)será concluída sem lançar uma exceção.

Estou interpretando mal isso ou não isfaço exatamente as mesmas verificações, apenas em uma única linha, sem a necessidade de criar explicitamente outra variável local para a verificação nula?

Quente e
fonte
1
você está usando myObjRef posteriormente no código? se estiver, não precisará do MyPropgetter após essa mudança.
Padrão de

Respostas:

145

Porque há apenas um elenco. Compare isto:

if (myObj.myProp is MyType) // cast #1
{
    var myObjRef = (MyType)myObj.myProp; // needs to be cast a second time
                                         // before using it as a MyType
    ...
}

para isso:

var myObjRef = myObj.myProp as MyType; // only one cast
if (myObjRef != null)
{
    // myObjRef is already MyType and doesn't need to be cast again
    ...
}

C # 7.0 oferece suporte a uma sintaxe mais compacta usando correspondência de padrões :

if (myObj.myProp is MyType myObjRef)
{
    ...
}
Jeff E
fonte
3
exatamente. usar 'is' é basicamente fazer algo como return ((myProp as MyType) == null)
Bambu
2
No que diz respeito às mudanças, porém, isso é muito minucioso. A verificação de nulo será bastante comparável à segunda verificação de tipo. aspode ser alguns nanossegundos mais rápido, mas considero isso uma microotimização prematura.
Servy de
4
Observe também que a versão original não é thread-safe. O valor de myObjou myProppode ser alterado (por outro encadeamento) entre o ise o elenco, causando um comportamento indesejável.
Jeff E
1
Também devo acrescentar que o uso de as+ != nulltambém executará o !=operador substituído de MyTypese definido (mesmo se myObjReffor nulo). Embora na maioria dos casos isso não seja um problema (especialmente se você implementá-lo corretamente), em alguns casos extremos (código ruim, desempenho) pode não ser desejado. (teria que ser muito extremo, no entanto)
Chris Sinclair
1
@Chris: Certo, a tradução correta do código usaria object.ReferenceEquals(null, myObjRef).
Ben Voigt
10

A melhor opção é usar a correspondência de padrões assim:

if (value is MyType casted){
    //Code with casted as MyType
    //value is still the same
}
//Note: casted can be used outside (after) the 'if' scope, too
Francesco Cattoni
fonte
Como exatamente este é melhor do que o segundo fragmento da pergunta?
Victor Yarema
O segundo fragmento da pergunta se refere ao uso básico de is (sem a declaração de variável) e, nesse caso, você verificará o tipo duas vezes (uma na declaração is e outra antes do elenco)
Francesco Cattoni
6

Não há informações ainda sobre o que realmente acontece abaixo da cintura. Dê uma olhada neste exemplo:

object o = "test";
if (o is string)
{
    var x = (string) o;
}

Isso se traduz no seguinte IL:

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  ldnull      
IL_000E:  cgt.un      
IL_0010:  stloc.1     
IL_0011:  ldloc.1     
IL_0012:  brfalse.s   IL_001D
IL_0014:  nop         
IL_0015:  ldloc.0     // o
IL_0016:  castclass   System.String
IL_001B:  stloc.2     // x
IL_001C:  nop         
IL_001D:  ret   

O que importa aqui são as chamadas isinste castclass- ambas relativamente caras. Se você comparar isso com a alternativa, verá que só faz uma isinstverificação:

object o = "test";
var oAsString = o as string;
if (oAsString != null)
{

}

IL_0000:  nop         
IL_0001:  ldstr       "test"
IL_0006:  stloc.0     // o
IL_0007:  ldloc.0     // o
IL_0008:  isinst      System.String
IL_000D:  stloc.1     // oAsString
IL_000E:  ldloc.1     // oAsString
IL_000F:  ldnull      
IL_0010:  cgt.un      
IL_0012:  stloc.2     
IL_0013:  ldloc.2     
IL_0014:  brfalse.s   IL_0018
IL_0016:  nop         
IL_0017:  nop         
IL_0018:  ret  

Também vale a pena mencionar que um tipo de valor usará em unbox.anyvez de castclass:

object o = 5;
if (o is int)
{
    var x = (int)o;
}

IL_0000:  nop         
IL_0001:  ldc.i4.5    
IL_0002:  box         System.Int32
IL_0007:  stloc.0     // o
IL_0008:  ldloc.0     // o
IL_0009:  isinst      System.Int32
IL_000E:  ldnull      
IL_000F:  cgt.un      
IL_0011:  stloc.1     
IL_0012:  ldloc.1     
IL_0013:  brfalse.s   IL_001E
IL_0015:  nop         
IL_0016:  ldloc.0     // o
IL_0017:  unbox.any   System.Int32
IL_001C:  stloc.2     // x
IL_001D:  nop         
IL_001E:  ret   

Observe, entretanto, que isso não se traduz necessariamente em um resultado mais rápido, como podemos ver aqui . Não parece ter havido melhorias desde que pergunta foi feita, porém: moldes parecem ser realizada o mais rápido que costumava ser, mas ase linqagora são cerca de 3 vezes mais rápido.

Jeroen Vannevel
fonte
4

Aviso de recompactação:

"Type check and direct cast can be replaced with try cast and check for null"

Ambos funcionarão, depende de como seu código se adapta melhor a você. No meu caso, simplesmente ignoro esse aviso:

//1st way is n+1 times of casting
if (x is A) ((A)x).Run();
else if (x is B) ((B)x).Run();
else if (x is C) ((C)x).Run();
else if (x is D) ((D)x).Run();
//...
else if (x is N) ((N)x).Run();    
//...
else if (x is Z) ((Z)x).Run();

//2nd way is z times of casting
var a = x as Type A;
var b = x as Type B;
var c = x as Type C;
//..
var n = x as Type N;
//..
var z = x as Type Z;
if (a != null) a.Run();
elseif (b != null) b.Run();
elseif (c != null) c.Run();
...
elseif (n != null) n.Run();
...
elseif (x != null) x.Run();

No meu código, a segunda forma é mais longa e pior.

Tom
fonte
1
Em seu exemplo do mundo real, há simplesmente um problema de design. Se você controlar os tipos, basta usar uma interface como IRunable. Se você não tem o controle, talvez possa usar dynamic?
M. Mimpen
3

Para mim, isso parece dependente de quais são as chances de que seja desse tipo ou não. Certamente seria mais eficiente fazer o elenco logo de cara se o objeto fosse desse tipo na maioria das vezes. Se for desse tipo apenas ocasionalmente, pode ser mais adequado verificar primeiro com is.

O custo de criação de uma variável local é muito insignificante em comparação com o custo da verificação de tipo.

Legibilidade e escopo são os fatores mais importantes para mim normalmente. Eu discordaria do ReSharper e usaria o operador "é" apenas por esse motivo; otimize mais tarde se for um verdadeiro gargalo.

(Presumo que você esteja usando apenas myObj.myProp is MyTypeuma vez nesta função)

Torre
fonte
0

Deve estar sugerindo uma segunda mudança também:

(MyType)myObj.myProp

para dentro

myObjRef

Isso salva um acesso à propriedade e um elenco, em comparação com o código original. Mas só é possível depois de mudar ispara as.

Ben Voigt
fonte
@Default: Não, não é. Isso não significa que não esteja no código.
Ben Voigt de
1
desculpe .. incompreendido. no entanto, (MyType)lançará uma exceção se a conversão falhar. assó retorna null.
Padrão de
@ Padrão: O elenco não falhará, porque o tipo já foi verificado com is(esse código está em questão).
Ben Voigt de
1
entretanto, re # deseja substituir esse código - o que significa que ele não estaria lá após a alteração sugerida.
Padrão de
Eu acho que estou seguindo seu pensamento aqui (apenas me levou algum tempo). Você quer dizer que a primeira linha está em algum lugar no código e essa linha seria simplificada após a sugestão Re # para a segunda linha?
Padrão de
0

Eu diria que isso é para fazer uma versão fortemente tipada de myObj.myProp, que é myObjRef. Isso deve ser usado quando você está referenciando esse valor no bloco, em vez de ter que fazer uma conversão.

Por exemplo, este:

myObjRef.SomeProperty

é melhor do que isso:

((MyType)myObj.myProp).SomeProperty
Jerad Rose
fonte