Faz sentido usar "como" em vez de um elenco, mesmo que não haja verificação nula? [fechadas]

351

Em blogs de desenvolvimento, exemplos de código on-line e (recentemente) até um livro, continuo tropeçando em códigos como este:

var y = x as T;
y.SomeMethod();

ou ainda pior:

(x as T).SomeMethod();

Isso não faz sentido para mim. Se você tem certeza que xé do tipo T, você deve usar um elenco direta: (T)x. Se você não tiver certeza, pode usá-lo, asmas precisa verificar nullantes de executar alguma operação. Tudo o que o código acima faz é transformar um (útil) InvalidCastExceptionem (inútil) NullReferenceException.

Eu sou o único que acha que isso é um abuso flagrante da aspalavra - chave? Ou eu perdi algo óbvio e o padrão acima realmente faz sentido?

Heinzi
fonte
56
Seria mais engraçado ver (beijar como S) .SteveIsSuchA (); Mas eu concordo, é um abuso.
precisa saber é o seguinte
5
É muito mais legal do que escrever ((T)x).SomeMethod(), não é? ;) (brincadeirinha, você está certo, é claro!)
Lucero
8
@ P Papai, eu discordo, perfeitamente boa pergunta (esse padrão de código realmente faz sentido) e muito útil. +1 na pergunta e uma careta para quem votar em fechar.
precisa saber é
9
Lucerno está certo, esse padrão de codificação é induzido tentando evitar parênteses. Incurável após ter sido exposto ao Lisp.
Hans Passant
13
Código otimizado (f as T).SomeMethod()
:;

Respostas:

252

Sua compreensão é verdadeira. Isso parece tentar otimizar para mim. Você deve usar um elenco normal quando tiver certeza do tipo. Além de gerar uma exceção mais sensata, também falha rapidamente. Se você está errado sobre a sua suposição sobre o tipo, o programa irá falhar imediatamente e você será capaz de ver a causa da falha imediatamente em vez de esperar por um NullReferenceExceptionou ArgumentNullExceptionou mesmo um algum erro lógico no futuro. Em geral, uma asexpressão que não é seguida por uma nullverificação em algum lugar é um cheiro de código.

Por outro lado, se você não tem certeza sobre o elenco e espera que ele falhe, use-o em asvez de um elenco normal envolvido com um try-catchbloco. Além disso, asrecomenda-se o uso de uma verificação de tipo seguida por uma conversão. Ao invés de:

if (x is SomeType)
   ((SomeType)x).SomeMethod();

que gera uma isinstinstrução para a ispalavra - chave e uma castclassinstrução para o elenco (realizando o elenco duas vezes), você deve usar:

var v = x as SomeType;
if (v != null)
    v.SomeMethod();

Isso gera apenas uma isinstinstrução. O método anterior possui uma falha em potencial em aplicativos multithread, pois uma condição de corrida pode fazer com que a variável mude de tipo após a isverificação ser bem-sucedida e falhar na linha de conversão. O último método não é propenso a esse erro.


A solução a seguir não é recomendada para uso em código de produção. Se você realmente odeia uma construção fundamental em C #, considere mudar para o VB ou algum outro idioma.

Caso alguém odeie desesperadamente a sintaxe do elenco, ele / ela pode escrever um método de extensão para imitar o elenco:

public static T To<T>(this object o) { // Name it as you like: As, Cast, To, ...
    return (T)o;
}

e use uma sintaxe pura [?]:

obj.To<SomeType>().SomeMethod()
Mehrdad Afshari
fonte
5
Eu acho que a condição de corrida é irrelevante. Se você está enfrentando esse problema, seu código não é seguro para threads e há maneiras mais confiáveis ​​de resolvê-lo do que usando a palavra-chave "as". +1 para o restante da resposta.
precisa saber é o seguinte
10
@RMorrisey: Tenho pelo menos um exemplo em mente: suponha que você tenha um cacheobjeto que outro thread tente invalidá-lo, definindo-o como null. Em cenários sem bloqueio, esse tipo de coisa pode surgir.
Mehrdad Afshari
10
is + cast é suficiente para acionar um aviso "Não converter desnecessariamente" do FxCop: msdn.microsoft.com/en-us/library/ms182271.aspx Esse deve ser um motivo suficiente para evitar a construção.
David Schmitt
2
Você deve evitar ativar os métodos de extensão Object. O uso do método em um tipo de valor fará com que ele seja encaixotado desnecessariamente.
MgSam
2
@MgSam Obviamente, esse caso de uso não faz sentido para o Tométodo aqui, pois apenas converte na hierarquia de herança, o que para tipos de valor envolve boxe de qualquer maneira. Obviamente, toda a ideia é mais teórica do que séria.
Mehrdad Afshari 28/11
42

IMHO, asapenas faz sentido quando combinado com uma nullverificação:

var y = x as T;
if (y != null)
    y.SomeMethod();
Rubens Farias
fonte
40

O uso de 'as' não aplica conversões definidas pelo usuário, enquanto o elenco as utilizará quando apropriado. Isso pode ser uma diferença importante em alguns casos.

Larry Fix
fonte
5
Isso é importante lembrar. Eric Lippert analisa isso aqui: blogs.msdn.com/ericlippert/archive/2009/10/08/…
P Daddy
5
Bom comentário, P! Se seu código depende dessa distinção, no entanto, eu diria que há uma sessão de depuração tarde da noite no seu futuro.
TrueWill 26/01
36

Eu escrevi um pouco sobre isso aqui:

http://blogs.msdn.com/ericlippert/archive/2009/10/08/what-s-the-difference-between-as-and-cast-operators.aspx

Eu entendo o seu ponto. E eu concordo com o objetivo: que um operador de conversão se comunique "Tenho certeza de que esse objeto pode ser convertido para esse tipo e estou disposto a arriscar uma exceção se estiver errado", enquanto um operador "como" se comunica "Não tenho certeza de que este objeto possa ser convertido para esse tipo; me dê um nulo se estiver errado".

No entanto, há uma diferença sutil. (x como T). O que quer que () comunique "Eu sei não apenas que x pode ser convertido em um T, mas, além disso, que isso envolve apenas conversões de referência ou unboxing e, além disso, que x não é nulo". Isso comunica informações diferentes de ((T) x). Qualquer que seja (), e talvez seja isso que o autor do código pretende.

Eric Lippert
fonte
11
Discordo da sua defesa especulativa do autor do código em sua última frase. ((T)x).Whatever() também comunica que xnão se destina a ser nulo, e eu duvido muito que um autor normalmente se importe se a conversão Tocorre apenas com conversões de referência ou de unboxing, ou se requer uma conversão definida pelo usuário ou que altere a representação. Afinal, se eu definir public static explicit operator Foo(Bar b){}, é claramente minha intenção que Barseja considerada compatível Foo. É raro que eu queira evitar essa conversão.
P Daddy
4
Bem, talvez a maioria dos autores de código não faça essa distinção sutil. Eu pessoalmente poderia ser, mas se fosse, acrescentaria um comentário nesse sentido.
Eric Lippert
16

Eu sempre vi referências a este artigo enganoso como evidência de que "como" é mais rápido que o lançamento.

Um dos aspectos enganosos mais óbvios deste artigo é o gráfico, que não indica o que está sendo medido: suspeito que esteja medindo as projeções com falha (onde "como" é obviamente muito mais rápido, pois nenhuma exceção é lançada).

Se você dedicar algum tempo para fazer as medições, verá que o elenco é, como seria de esperar, mais rápido que "as" quando o elenco é bem-sucedido.

Suspeito que esse seja um dos motivos para o uso de "cult de carga" da palavra-chave as em vez de um elenco.

Joe
fonte
2
Obrigado pelo link, isso é muito interessante. De como eu entendi o artigo, ele faz comparar o caso não é exceção. No entanto, o artigo foi escrito para .net 1.1 e os comentários apontam que isso mudou no .net 2.0: o desempenho agora é quase igual, com o prefixo convertido sendo um pouco mais rápido.
Heinzi 26/01
11
O artigo indica que ele está comparando o caso de não exceção, mas eu fiz alguns testes há muito tempo e não consegui reproduzir os resultados reivindicados, mesmo com o .NET 1.x. E como o artigo não fornece o código usado para executar o benchmark, é impossível dizer o que está sendo comparado.
Joe
"Cargo cult" - perfeito. Confira "Cargo Cult Science Richard Feynman" para informações completas.
Bob Denny
11

A conversão direta precisa de um par de parênteses mais do que a aspalavra - chave. Portanto, mesmo no caso de você ter 100% de certeza do tipo, isso reduz a confusão visual.

No entanto, concordamos com a exceção. Mas pelo menos para mim, a maioria dos usos se asresume a procurar nulldepois, o que acho melhor do que pegar uma exceção.

Joey
fonte
8

99% do tempo em que uso "como" ocorre quando não tenho certeza de qual é o tipo de objeto real

var x = obj as T;
if(x != null){
 //x was type T!
}

e eu não quero capturar exceções explícitas de elenco nem fazer elenco duas vezes, usando "is":

//I don't like this
if(obj is T){
  var x = (T)obj; 
}
Max Galkin
fonte
8
Você acabou de descrever o caso de uso adequado para as. Qual é o outro 1%?
P Daddy
Em um erro de digitação? =) Eu quis dizer 99% do tempo em que utilizo esse trecho de código exato , enquanto às vezes posso usar "como" em uma chamada de método ou em outro local.
precisa saber é o seguinte
D'oh, e como isso é menos útil que a segunda resposta popular ???
precisa saber é o seguinte
2
+1 Eu concordo que chamar isso é tão valioso quanto a resposta de Rubens Farias - espero que as pessoas venham aqui e este seja um exemplo útil
Ruben Bartelink
8

É só porque as pessoas gostam da aparência, é muito legível.

Vamos ser sinceros: o operador de conversão / conversão em idiomas do tipo C é bastante terrível, em termos de legibilidade. Eu gostaria que o C # adotasse a sintaxe Javascript de:

object o = 1;
int i = int(o);

Ou defina um tooperador, o equivalente de conversão deas :

object o = 1;
int i = o to int;
JulianR
fonte
Só para você saber, a sintaxe JavaScript mencionada também é permitida em C ++.
P Daddy
@PDaddy: Não é uma 100% sintaxe alternativa compatível directa embora e não se destina a que (X operador vs construtor de conversão)
Ruben Bartelink
Eu preferiria usar a sintaxe C ++ de dynamic_cast<>()(e similar). Você está fazendo algo feio, deve parecer feio.
Tom Hawtin - defina
5

As pessoas gostam astanto porque as faz se sentirem protegidas de exceções ... Como garantia em uma caixa. Um cara coloca uma garantia chique na caixa, porque ele quer que você se sinta todo quente e quentinho por dentro. Você acha que coloca essa caixinha debaixo do travesseiro à noite, a Fada da Garantia pode descer e deixar um quarto, não é mesmo, Ted?

Voltar ao tópico ... ao usar uma conversão direta, existe a possibilidade de uma exceção de conversão inválida. Portanto, as pessoas se aplicam ascomo uma solução geral para todas as suas necessidades de fundição porque as(por si só) nunca lançará uma exceção. Mas o engraçado é que, no exemplo que você deu(x as T).SomeMethod(); você está trocando uma exceção de conversão inválida por uma exceção de referência nula. O que ofusca o problema real quando você vê a exceção.

Eu geralmente não uso asmuito. Eu prefiro o isteste porque, para mim, parece mais legível e faz mais sentido do que tentar um elenco e verificar se há nulo.

Prumo
fonte
2
"Prefiro o teste is" - "is" seguido de uma conversão é, obviamente, mais lento que "as", seguido de um teste para null (assim como "IDictionary.ContainsKey" seguido de desreferenciamento usando o indexador, é mais lento que "IDictionary.TryGetValue "). Mas se você achar mais legível, sem dúvida a diferença raramente é significativa.
Joe
A afirmação importante na parte do meio é como as pessoas se aplicam ascomo uma solução geral porque as faz se sentirem seguras.
Bob
5

Essa deve ser uma das minhas maiores irritações .

O D&E da Stroustrup e / ou algum post do blog que não consigo encontrar agora discute a noção de tooperador que abordaria o argumento de https://stackoverflow.com/users/73070/johannes-rossel (ou seja, a mesma sintaxe que asmas comDirectCast semântica) )

A razão pela qual isso não foi implementado é porque um elenco deve causar dor e ser feio, para que você se afaste do uso.

Pena que programadores 'inteligentes' (muitas vezes autores de livros (Juval Lowy IIRC)) contornem isso abusando asdessa maneira (o C ++ não oferece as, provavelmente por esse motivo).

Mesmo o VB tem mais consistência em ter uma sintaxe uniforme que o força a escolher um TryCastou DirectCaste decidir-se !

Ruben Bartelink
fonte
+1. Você provavelmente quis dizer DirectCast comportamento , não sintaxe .
Heinzi 26/01
@ Heinzi: Ta por +1. Bom ponto. Decidiu ser um espertinho e usar semantics: P
Ruben Bartelink
Dado que o C # não tem pretensão de compatibilidade com C, C ++ ou Java, me vejo irritado com algumas das coisas que ele empresta dessas linguagens. Vai além de "Eu sei que este é um X" e "Eu sei que este não é um X, mas pode ser representado como um", para "Eu sei que este não é um X e pode não ser realmente representável como um". , mas me dê um X de qualquer maneira. " Eu pude ver a utilidade de um double-to-intelenco que falharia se doublenão representasse um valor exato que pudesse caber em um Int32, mas ter (int)-1.5rendimento -1 é simplesmente feio.
Supercat
@ supercat Sim, mas o design da linguagem não é fácil, como todos sabemos - veja o conjunto de vantagens e desvantagens envolvidas em C # nullables. O único antídoto conhecido é a leitura regular de C # nas edições Depth à medida que elas aparecem :) Felizmente, estou mais preocupado em entender as nuances do F # hoje em dia e é muito mais sensato em relação a muitos desses assuntos.
Ruben Bartelink
@ RubenBartelink: Não estou muito claro quais problemas exatos os tipos anuláveis ​​deveriam resolver, mas eu acho que na maioria dos casos seria melhor ter um MaybeValid<T>com dois campos públicos IsValide Valuequal código poderia fazer como achar melhor. Isso teria permitido, por exemplo MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }. Isso não apenas salvaria pelo menos duas operações de cópia em comparação com Nullable<T>, mas também poderia valer com qualquer tipo T- e não apenas classes.
Supercat
2

Acredito que a aspalavra - chave possa ser vista como uma versão mais elegante dynamic_castdo C ++.

Andrew Garrison
fonte
Eu diria que uma conversão direta em C # é mais parecida dynamic_castcom C ++.
P Daddy
Eu acho que o elenco direto em c # é mais equivalente ao static_cast em c ++.
Andrew Garrison
2
@ Ruben Bartelink: Apenas retorna nulo com ponteiros. Com referências, que você deve usar sempre que possível, ele lança std::bad_cast.
P Daddy
11
@ Andrew Garrison: static_castnão executa nenhuma verificação do tipo de tempo de execução. Não há elenco semelhante a isso em C #.
P Daddy
Infelizmente, eu não sabia que você poderia usar os modelos em referências, pois eu os usei apenas em ponteiros, mas P Daddy está absolutamente correto!
Andrew Garrison
1

Provavelmente é mais popular sem motivo técnico, mas apenas porque é mais fácil de ler e mais intuitivo. (Sem dizer que é melhor apenas tentar responder à pergunta)

Jla
fonte
1

Um motivo para usar "como":

T t = obj as T;
 //some other thread changes obj to another type...
if (t != null) action(t); //still works

Em vez de (código incorreto):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}
Rauhotz
fonte
2
Se você tem condições de corrida neste feio, você tem problemas maiores (mas concorda que é uma boa amostra para ir com os outros, então +1
Ruben Bartelink
-1, pois isso perpetua uma falácia. Se outros threads puderem alterar o tipo de obj, você ainda terá problemas. A afirmação "// ainda funciona" é muito improvável que seja verdadeira, pois t será usado como um ponteiro para T, mas aponta para a memória que não é mais um T. Nenhuma solução funcionará quando o outro encadeamento alterar o tipo de obj enquanto a ação (t) está em andamento.
Stephen C. Steel
5
@ Stephen C. Steel: Você parece estar bastante confuso. Alterar o tipo de objsignificaria alterar a objprópria variável para manter uma referência a outro objeto. Não alteraria o conteúdo da memória na qual reside o objeto originalmente referenciado por obj. Esse objeto original permaneceria inalterado e a tvariável ainda manteria uma referência a ele.
P Daddy
11
@ P papai - Eu acho que você está correto, e eu estava errado: se obj foi recuperado de um objeto T para um objeto T2, então t ainda estaria apontando para o antigo objeto T. Como t ainda faz referência ao objeto antigo, ele não pode ser coletado como lixo, portanto o objeto T antigo permanecerá válido. Meus circuitos detectores de condição de corrida foram treinados em C ++, onde código semelhante usando dynamic_cast seria um problema em potencial.
Stephen C. Steel