¿Tiene sentido usar "como" en lugar de un lanzamiento, incluso si no hay un cheque nulo? [cerrado]

351

En blogs de desarrollo, ejemplos de código en línea e (recientemente) incluso un libro, sigo tropezando con código como este:

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

o, peor aún:

(x as T).SomeMethod();

Eso no tiene sentido para mí. Si está seguro de que xes de tipo T, se debe utilizar un molde directo: (T)x. Si no está seguro, puede usarlo, aspero debe verificarnull antes de realizar alguna operación. Todo lo que hace el código anterior es convertir un (útil) InvalidCastExceptionen un (inútil) NullReferenceException.

¿Soy el único que piensa que esto es un abuso flagrante de la aspalabra clave? ¿O me perdí algo obvio y el patrón anterior realmente tiene sentido?

Heinzi
fuente
56
Sería más divertido ver (beso como S) .SteveIsSuchA (); Pero estoy de acuerdo, es un abuso.
SwDevMan81
55
Es mucho más genial que escribir ((T)x).SomeMethod(), ¿no? ;) (es broma, ¡tienes razón, por supuesto!)
Lucero el
8
@P Daddy No estoy de acuerdo, es una pregunta perfectamente buena (¿este patrón de código realmente tiene sentido?), Y muy útil. +1 a la pregunta y fruncir el ceño a cualquiera que vote para cerrar.
MarkJ
99
Lucerno tiene razón, este patrón de codificación se induce al tratar de evitar paréntesis. Incurable después de haber estado expuesto a Lisp.
Hans Passant
13
Código optimizado (f as T).SomeMethod()
:;

Respuestas:

252

Tu comprensión es verdadera. Eso suena como tratar de micro-optimizar para mí. Debe usar un yeso normal cuando esté seguro del tipo. Además de generar una excepción más sensata, también falla rápidamente. Si se equivoca sobre su hipótesis sobre el tipo, el programa fallará inmediatamente y usted será capaz de ver la causa del fracaso de inmediato en lugar de esperar a que una NullReferenceExceptiono ArgumentNullExceptionni siquiera un error lógico en algún momento en el futuro. En general, una asexpresión que no es seguida por un nullcheque en alguna parte es un olor a código.

Por otro lado, si no está seguro sobre el yeso y espera que falle, debe usarlo en aslugar de un yeso normal envuelto con un try-catchbloque. Además, asse recomienda el uso de una verificación de tipo seguida de un yeso. En vez de:

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

que genera una isinstinstrucción para la ispalabra clave y una castclassinstrucción para el reparto (realizar efectivamente el reparto dos veces), debe usar:

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

Esto solo genera una isinstinstrucción. El primer método tiene una falla potencial en aplicaciones de subprocesos múltiples, ya que una condición de carrera puede hacer que la variable cambie su tipo después de que la isverificación se haya realizado correctamente y falle en la línea de lanzamiento. El último método no es propenso a este error.


La siguiente solución no se recomienda para su uso en el código de producción. Si realmente odias una construcción tan fundamental en C #, podrías considerar cambiar a VB u otro lenguaje.

En caso de que uno odie desesperadamente la sintaxis del elenco, puede escribir un método de extensión para imitar el elenco:

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

y use una sintaxis ordenada [?]:

obj.To<SomeType>().SomeMethod()
Mehrdad Afshari
fuente
55
Creo que la condición de la carrera es irrelevante. Si tiene este problema, su código no es seguro para subprocesos y existen formas más confiables de resolverlo que usando la palabra clave "as". +1 para el resto de la respuesta.
RMorrisey
10
@RMorrisey: Tengo al menos un ejemplo en mente: suponga que tiene un cacheobjeto que otro hilo intenta invalidar configurándolo en null. En escenarios sin bloqueo, pueden surgir este tipo de cosas.
Mehrdad Afshari
10
is + cast es suficiente para activar una advertencia de "No lanzar innecesariamente" desde FxCop: msdn.microsoft.com/en-us/library/ms182271.aspx Esa debería ser razón suficiente para evitar la construcción.
David Schmitt
2
Debe evitar hacer métodos de extensión en Object. El uso del método en un tipo de valor hará que se encuadre innecesariamente.
MgSam
2
@MgSam Obviamente, este caso de uso no tiene sentido para el Tométodo aquí, ya que solo se convierte en la jerarquía de herencia, lo que para los tipos de valor implica el boxeo de todos modos. Por supuesto, toda la idea es más teórica que seria.
Mehrdad Afshari
42

En mi humilde opinión, astiene sentido cuando se combina con un nullcheque:

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

El uso de 'as' no aplica las conversiones definidas por el usuario, mientras que el elenco las usará cuando corresponda. Esa puede ser una diferencia importante en algunos casos.

Larry Fix
fuente
55
Esto es importante para recordar. Eric Lippert repasa eso aquí: blogs.msdn.com/ericlippert/archive/2009/10/08/…
P Daddy
55
Buen comentario, P! Sin embargo, si su código depende de esta distinción, diría que hay una sesión de depuración nocturna en su futuro.
TrueWill
36

Escribí un poco sobre esto aquí:

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

Entiendo tu punto. Y estoy de acuerdo con el objetivo: que un operador de reparto comunica "Estoy seguro de que este objeto se puede convertir a ese tipo, y estoy dispuesto a arriesgar una excepción si me equivoco", mientras que un operador "como" se comunica "No estoy seguro de que este objeto se pueda convertir a ese tipo; dame un valor nulo si me equivoco".

Sin embargo, hay una sutil diferencia. (x como T). Lo que sea que () comunique "Sé no solo que x puede convertirse en una T, sino que, además, eso implica solo conversiones de referencia o unboxing y, además, que x no es nulo". Eso comunica información diferente a ((T) x). Lo que sea (), y quizás eso es lo que pretende el autor del código.

Eric Lippert
fuente
1
No estoy de acuerdo con tu defensa especulativa del autor del código en tu última oración. ((T)x).Whatever() también comunica que xno está [destinado a ser] nulo, y dudo mucho que un autor normalmente se preocupe si la conversión se Tproduce solo con conversiones de referencia o unboxing, o si requiere una conversión definida por el usuario o que cambie la representación. Después de todo, si lo defino public static explicit operator Foo(Bar b){}, entonces es claramente mi intención la que Barse considera compatible con Foo. Es raro que quiera evitar esta conversión.
P Daddy
44
Bueno, quizás la mayoría de los autores de código no harían esa sutil distinción. Yo personalmente podría serlo, pero si lo fuera, agregaría un comentario al respecto.
Eric Lippert el
16

A menudo he visto referencias a este artículo engañoso. como evidencia de que "as" es más rápido que el casting.

Uno de los aspectos engañosos más obvios de este artículo es el gráfico, que no indica lo que se está midiendo: sospecho que la medición ha fallado lanzamientos (donde "as" es obviamente mucho más rápido ya que no se produce ninguna excepción).

Si se toma el tiempo para hacer las mediciones, verá que el lanzamiento es, como era de esperar, más rápido que "como" cuando el lanzamiento tiene éxito.

Sospecho que esta puede ser una razón para el uso de "culto de carga" de la palabra clave como en lugar de un elenco.

Joe
fuente
2
Gracias por el enlace, eso es muy interesante. De la forma en que entendí el artículo, que hace comparar el caso no es una excepción. Sin embargo, el artículo fue escrito para .net 1.1, y los comentarios señalan que esto cambió en .net 2.0: el rendimiento ahora es casi igual, con el prefijo emitido incluso un poco más rápido.
Heinzi
1
El artículo implica que está comparando el caso de no excepción, pero hice algunas pruebas hace mucho tiempo y no pude reproducir sus resultados reclamados, incluso con .NET 1.x. Y dado que el artículo no proporciona el código utilizado para ejecutar el punto de referencia, es imposible decir qué se está comparando.
Joe
"Culto de carga" - perfecto. Echa un vistazo a "Cargo Cult Science Richard Feynman" para obtener la información completa.
Bob Denny,
11

El reparto directo necesita un par de paréntesis más que la aspalabra clave. Entonces, incluso en el caso de que esté 100% seguro de cuál es el tipo, reduce el desorden visual.

Sin embargo, estuvo de acuerdo en lo de la excepción. Pero al menos para mí, la mayoría de los usos de asreducir se reducen para verificar nulldespués, lo que me parece más agradable que detectar una excepción.

Joey
fuente
8

El 99% de las veces que uso "como" es cuando no estoy seguro de cuál es el tipo de objeto real

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

y no quiero capturar excepciones explícitas ni emitir dos veces, usando "is":

//I don't like this
if(obj is T){
  var x = (T)obj; 
}
Max Galkin
fuente
8
Acaba de describir el caso de uso adecuado para as. ¿Cuál es el otro 1%?
P papá el
En un error tipográfico? =) Quise decir el 99% del tiempo que uso este fragmento de código exacto , mientras que a veces puedo usar "como" en una llamada al método o en otro lugar.
Max Galkin el
D'oh, ¿y cómo es eso menos útil que la segunda respuesta popular?
Max Galkin el
2
+1 Estoy de acuerdo en que decir esto es tan valioso como la respuesta de Rubens Farias: es de esperar que la gente venga aquí y este será un ejemplo útil
Ruben Bartelink
8

Es solo porque a la gente le gusta cómo se ve, es muy legible.

Seamos realistas: el operador de conversión / conversión en lenguajes tipo C es bastante terrible, en cuanto a legibilidad. Me gustaría que C # adoptara la sintaxis Javascript de:

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

O defina un tooperador, el equivalente de lanzamiento de as:

object o = 1;
int i = o to int;
JulianR
fuente
Para que lo sepas, la sintaxis de JavaScript que mencionas también está permitida en C ++.
P Daddy
@PDaddy: Sin embargo, no es una sintaxis alternativa directa 100% compatible y no está destinada a eso (operador X vs constructor de conversión)
Ruben Bartelink
Prefiero que use la sintaxis de C ++ de dynamic_cast<>()(y similar). Estás haciendo algo feo, debería verse feo.
Tom Hawtin - tackline
5

A la gente le gustaas mucho porque los hace sentir a salvo de las excepciones ... Como garantía en una caja. Un chico pone una garantía elegante en la caja porque quiere que te sientas cálido y tostado por dentro. Crees que pones esa cajita debajo de tu almohada por la noche, el Hada de la Garantía podría bajar y dejar un cuarto, ¿estoy en lo cierto, Ted?

Volviendo al tema ... cuando se usa una transmisión directa, existe la posibilidad de una excepción de transmisión no válida. Por lo tanto, las personas se aplican ascomo una solución general a todas sus necesidades de lanzamiento porque as(por sí solo) nunca arrojará una excepción. Pero lo curioso de eso, está en el ejemplo que diste(x as T).SomeMethod(); , está intercambiando una excepción de conversión no válida por una excepción de referencia nula. Lo que ofusca el verdadero problema cuando ves la excepción.

Generalmente no uso asdemasiado. Prefiero la isprueba porque, para mí, parece más legible y tiene más sentido que probar un yeso y verificar si es nulo.

Beto
fuente
2
"Prefiero la prueba is" - "is" seguido de una conversión es, por supuesto, más lento que "as" seguido de una prueba para nulo (al igual que "IDictionary.ContainsKey" seguido de desreferencia usando el indexador es más lento que "IDictionary. TryGetValue "). Pero si lo encuentra más legible, sin duda la diferencia rara vez es significativa.
Joe
La declaración importante en la parte central es cómo las personas aplican ascomo una solución general porque les hace sentirse seguras.
Bob
5

Este tiene que ser uno de mis principales manías .

D&E de Stroustrup y / o alguna publicación de blog que no puedo encontrar en este momento discute la noción de un tooperador que abordaría el punto hecho por https://stackoverflow.com/users/73070/johannes-rossel (es decir, la misma sintaxis aspero con DirectCastsemántica )

La razón por la que esto no se implementó es porque un yeso debería causar dolor y ser feo, por lo que no podrá usarlo.

Lástima que los programadores 'inteligentes' (a menudo autores de libros (Juval Lowy IIRC)) lo eviten abusando asde esta manera (C ++ no ofrece una as, probablemente por esta razón).

¡Incluso VB tiene más consistencia al tener una sintaxis uniforme que te obliga a elegir un TryCasto DirectCasty tomar una decisión !

Ruben Bartelink
fuente
+1. Probablemente quisiste decir DirectCast comportamiento , no sintaxis .
Heinzi
@Heinzi: Ta para +1. Buen punto. Decidió ser un smartarse y usar semanticsen su lugar: P
Ruben Bartelink
Dado que C # no pretende ser compatible con C, C ++ o Java, me molesta algunas de las cosas que toma prestados de esos lenguajes. Va más allá de "Sé que esto es una X" y "Sé que esto no es una X, pero se puede representar como uno", a "Sé que esto no es una X, y podría no ser realmente representable como uno , pero dame una X de todos modos ". Pude ver la utilidad de un double-to-intlanzamiento que fallaría si doubleno representara un valor exacto que pudiera caber en un Int32, pero tener un (int)-1.5rendimiento -1 es simplemente feo.
supercat
@supercat Sí, pero el diseño del lenguaje no es fácil, como todos sabemos: observe el conjunto de compensaciones involucradas en C # nulables. El único antídoto conocido es la lectura regular de C # en las ediciones en profundidad a medida que aparecen :) Afortunadamente, estoy más preocupado por entender los matices de F # en estos días y es mucho más sensato en muchos de estos asuntos.
Ruben Bartelink
@RubenBartelink: no estoy muy claro qué problemas exactos se suponía que los tipos anulables debían resolver, pero creo que en la mayoría de los casos habría sido mejor tener un MaybeValid<T>con dos campos públicos IsValidy Valuequé código podría resolver como mejor le parezca. Eso hubiera permitido, por ejemplo MaybeValid<TValue> TryGetValue(TKey key) { var ret = default(MaybeValid<TValue>); ret.IsValid = dict.TryGetValue(key, out ret.Value); return ret; }. Eso no solo ahorraría al menos dos operaciones de copia en comparación Nullable<T>, sino que también podría valer con cualquier tipo, no Tsolo clases.
supercat
2

Creo que la aspalabra clave podría considerarse como una versión más elegante dynamic_castde C ++.

Andrew Garrison
fuente
Yo diría que un elenco directo en C # es más parecido dynamic_casta C ++.
P Daddy
Creo que la conversión directa en C # es más equivalente a la static_cast en C ++.
Andrew Garrison el
2
@Ruben Bartelink: solo devuelve nulo con punteros. Con referencias, que deberías usar cuando sea posible, arroja std::bad_cast.
P Daddy
1
@ Andrew Garrison: static_castno realiza ninguna verificación del tipo de tiempo de ejecución. No hay un reparto similar a esto en C #.
P Daddy
Lamentablemente, no sabía que incluso podrías usar los moldes en las referencias, ya que solo los he usado en punteros, ¡pero P Daddy tiene toda la razón!
Andrew Garrison el
1

Probablemente sea más popular sin ninguna razón técnica, sino solo porque es más fácil de leer y más intuitivo. (No decir que lo hace mejor solo tratando de responder la pregunta)

Jla
fuente
1

Una razón para usar "como":

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

En lugar de (código incorrecto):

if (obj is T)
{
     //bang, some other thread changes obj to another type...
     action((T)obj); //InvalidCastException
}
Rauhotz
fuente
2
Si tienes condiciones de carrera en este feo, tienes problemas más grandes (pero estoy de acuerdo en que es una buena muestra para ir con los demás, así que +1
Ruben Bartelink el
-1 ya que esto perpetúa una falacia. Si otros hilos pueden estar cambiando el tipo de obj, entonces todavía tiene problemas. La afirmación "// todavía funciona" es muy difícil de mantener, ya que t se usará como puntero a T, pero apunta a la memoria que ya no es una T. Ninguna de las soluciones funcionará cuando el otro hilo cambie el tipo de obj mientras la acción (t) está en progreso.
Stephen C. Steel el
55
@Stephen C. Steel: Parece que estás bastante confundido. Cambiar el tipo de objsignificaría cambiar la objvariable en sí para contener una referencia a otro objeto. No alteraría el contenido de la memoria en la que reside el objeto originalmente referenciado por obj. Este objeto original permanecería sin cambios, y la tvariable aún tendría una referencia a él.
P Daddy
1
@P Daddy: creo que estás en lo correcto, y me equivoqué: si obj se recuperó de un objeto T a un objeto T2, entonces t aún estaría apuntando al antiguo objeto T. Como t todavía hace referencia al objeto antiguo, no se puede recolectar basura, por lo que el objeto T antiguo seguirá siendo válido. Mis circuitos detectores de condición de carrera fueron entrenados en C ++, donde un código similar usando dynamic_cast sería un problema potencial.
Stephen C. Steel el