¿Reparto directo vs operador 'como'?

710

Considere el siguiente código:

void Handler(object o, EventArgs e)
{
   // I swear o is a string
   string s = (string)o; // 1
   //-OR-
   string s = o as string; // 2
   // -OR-
   string s = o.ToString(); // 3
}

¿Cuál es la diferencia entre los tres tipos de lanzamiento (está bien, el tercero no es un lanzamiento, pero se obtiene la intención). ¿Cuál debería ser el preferido?

nullDev
fuente
1
No es un duplicado, pero también hay algunas discusiones de rendimiento en una pregunta anterior .
cortar el
8
4to string s = Convert.ToString(o):; 5to: string s = $"{o}"(o equivalentemente el string.Formatformulario para C # anterior)
Earth Engine

Respuestas:

834
string s = (string)o; // 1

Lanza InvalidCastException si ono es un string. De lo contrario, se asigna oa s, incluso si oes así null.

string s = o as string; // 2

Asigna nulla ssi ono es un stringo si oes null. Por esta razón, no puede usarlo con tipos de valor (el operador nunca podría regresar nullen ese caso). De lo contrario, se asigna oa s.

string s = o.ToString(); // 3

Causa una NullReferenceException si oes null. Asigna lo que sea que o.ToString()regrese s, sin importar de qué tipo osea.


Use 1 para la mayoría de las conversiones: es simple y directo. Casi nunca uso 2 ya que si algo no es del tipo correcto, generalmente espero que ocurra una excepción. Solo he visto la necesidad de este tipo de funcionalidad de retorno nulo con bibliotecas mal diseñadas que usan códigos de error (por ejemplo, retorno nulo = error, en lugar de usar excepciones).

3 no es un elenco y es solo una invocación de método. Úselo para cuando necesite la representación de cadena de un objeto que no sea de cadena.

Lijadora
fuente
2
Puede asignar 'nulo' a los tipos de valor cuando se define explícitamente, por ejemplo: int? yo; cadena s = "5"; i = s como int; // ahora es 5 s = nulo; i = s como int; // ahora es nulo
Anheledir
3
RE: Anheledir En realidad, sería nulo después de la primera llamada. Debe usar una función de conversión explícita para obtener el valor de una cadena.
Guvante
45
RE: Sander En realidad, hay otra muy buena razón para usar, ya que simplifica su código de verificación (Verificar nulo en lugar de verificar el tipo nulo y correcto) Esto es útil ya que muchas veces preferiría lanzar una excepción personalizada. Pero es muy cierto que ciegas como las llamadas son malas.
Guvante
55
# 2 es útil para cosas como métodos Equals donde no conoce el tipo de entrada. Sin embargo, en general, sí, sería preferible 1. Aunque es preferible a eso, obviamente, estaría usando el sistema de tipos para restringir a un tipo cuando solo espera uno :)
Calum
66
El n. ° 2 también es útil cuando tiene un código que podría hacer algo específico para un tipo especializado, pero de lo contrario no haría nada.
AnthonyWJones
349
  1. string s = (string)o;Úselo cuando algo definitivamente debería ser lo otro.
  2. string s = o as string;Úselo cuando algo podría ser lo otro.
  3. string s = o.ToString(); Úselo cuando no le importe lo que es, pero solo desea utilizar la representación de cadena disponible.
Molesto
fuente
1
Tengo la sensación de que esta respuesta suena bien, pero puede que no sea precisa.
j riv
1
Me gustan los dos primeros, pero agregaría "y está seguro de que no es nulo" a la tercera opción.
Uxonith
2
puede utilizar Elvis estos días para no tener que preocuparse de que (?.):? obj .ToString ()
Quibblesome
@Quibblesome - ¡gran respuesta pero tuve que parar para pensar en tu refutación! literalmente me sorprende que el lenguaje haya existido por más de 15 años. Parece que fue ayer cuando estábamos todos "nerviosos" tratando de convencer a los desarrolladores senior para que cambiaran a C #.
Griswald_911
1
Respuesta agradable de @Quibblesome: ¿se molestará si agrego lo que son 1/2/3 para que no sea necesario desplazarse hasta OP. ¡Yo con SO clasificaría las respuestas antiguas según los votos!
whytheq
29

Realmente depende de si sabes si oes una cadena y qué quieres hacer con ella. Si su comentario significa que orealmente es una cadena, preferiría el (string)oreparto directo , es poco probable que falle.

La mayor ventaja de usar el lanzamiento directo es que cuando falla, obtienes una InvalidCastException , que te dice más o menos lo que salió mal.

Con el asoperador, si ono es una cadena, sse establece en null, lo cual es útil si no está seguro y desea probar s:

string s = o as string;
if ( s == null )
{
    // well that's not good!
    gotoPlanB();
}

Sin embargo, si no realiza esa prueba, la usará smás tarde y se generará una excepción NullReferenceException . Estos tienden a ser más comunes y mucho más difíciles de rastrear una vez que ocurren en la naturaleza, ya que casi todas las líneas desreferencian una variable y pueden arrojar una. Por otro lado, si está intentando convertir a un tipo de valor (cualquier primitivo, o estructuras como DateTime ), debe usar la conversión directa; asno funcionará.

En el caso especial de convertir a una cadena, cada objeto tiene un ToString, por lo que su tercer método puede estar bien si ono es nulo y cree que el ToStringmétodo podría hacer lo que desea.

Blair Conrad
fuente
2
Una nota: puede usar ascon tipos de valores anulables . IE o as DateTimeno funcionará, pero o as DateTime?...
John Gibb
¿Por qué no usar en su if (s is string)lugar?
BornToCode
1
@BornToCode, para mí, en gran medida de preferencia personal. Dependiendo de lo que esté haciendo, a menudo después de ising, tendrá que lanzar de nuevo de todos modos, por lo que tiene el is y luego un yeso duro. Por alguna razón, el ascheque nulo me pareció mejor.
Blair Conrad
9

Si ya sabe a qué tipo puede emitir, use un molde de estilo C:

var o = (string) iKnowThisIsAString; 

Tenga en cuenta que solo con un elenco de estilo C puede realizar una coerción de tipo explícito.

Si no sabe si es el tipo deseado y lo va a usar si es así, use como palabra clave:

var s = o as string;
if (s != null) return s.Replace("_","-");

//or for early return:
if (s==null) return;

Tenga en cuenta que como no llamará a ningún tipo de operadores de conversión. Solo será no nulo si el objeto no es nulo y es nativo del tipo especificado.

Use ToString () para obtener una representación de cadena legible por humanos de cualquier objeto, incluso si no se puede convertir en cadena.

Mark Cidade
fuente
3
Esa es una pequeña trampa interesante con respecto a los operadores de conversión de tipos. Tengo algunos tipos para los que he creado conversiones, entonces debo tener cuidado con eso.
AnthonyWJones
7

La palabra clave as es buena en asp.net cuando usa el método FindControl.

Hyperlink link = this.FindControl("linkid") as Hyperlink;
if (link != null)
{
     ...
}

Esto significa que puede operar en la variable escrita en lugar de tener que emitirla objectcomo lo haría con una conversión directa:

object linkObj = this.FindControl("linkid");
if (link != null)
{
     Hyperlink link = (Hyperlink)linkObj;
}

No es una gran cosa, pero guarda líneas de código y asignación de variables, además es más legible

Glenn Slaven
fuente
6

'as' se basa en 'is', que es una palabra clave que verifica en tiempo de ejecución si el objeto es polimorfológicamente compatible (básicamente, si se puede realizar una conversión) y devuelve nulo si la verificación falla.

Estos dos son equivalentes:

Usando 'como':

string s = o as string;

Usando 'es':

if(o is string) 
    s = o;
else
    s = null;

Por el contrario, el elenco de estilo c también se realiza en tiempo de ejecución, pero arroja una excepción si no se puede realizar el elenco.

Solo para agregar un hecho importante:

La palabra clave 'as' solo funciona con tipos de referencia. Tú no puedes hacer:

// I swear i is an int
int number = i as int;

En esos casos tienes que usar casting.

Sergio Acosta
fuente
Gracias por señalar mi error, tienes razón. Edité la respuesta. Ups, lo siento.
Sergio Acosta
5

2 es útil para convertir a un tipo derivado.

Supongamos que a es un animal:

b = a as Badger;
c = a as Cow;

if (b != null)
   b.EatSnails();
else if (c != null)
   c.EatGrass();

obtendrá una alimentación con un mínimo de yesos.

Joel en Go
fuente
2
@Chirs Moutray, eso no siempre es posible, especialmente si se trata de una biblioteca.
Caviar desacelerado
5

Según los experimentos realizados en esta página: http://www.dotnetguru2.org/sebastienros/index.php/2006/02/24/cast_vs_as

(esta página tiene algunos errores de "referencia ilegal" a veces, así que simplemente actualice si es así)

La conclusión es que el operador "como" es normalmente más rápido que un yeso. A veces muchas veces más rápido, a veces apenas más rápido.

Peronsonally cosa "como" también es más legible.

Entonces, dado que es más rápido y "más seguro" (no arrojará una excepción), y posiblemente más fácil de leer, recomiendo usar "como" todo el tiempo.

Brady Moritz
fuente
4

"(string) o" dará como resultado una InvalidCastException ya que no hay conversión directa.

"o como cadena" dará como resultado que s sea una referencia nula, en lugar de que se genere una excepción.

"o.ToString ()" no es un elenco de ningún tipo en sí, es un método implementado por objeto y, por lo tanto, de una forma u otra, por cada clase en .net que "hace algo" con la instancia de se llama a la clase y devuelve una cadena.

No olvide que para convertir a cadena, también hay Convert.ToString (someType instanceOfThatType) donde someType es uno de un conjunto de tipos, esencialmente los tipos base de frameworks.

Robar
fuente
3

Todas las respuestas dadas son buenas, si pudiera agregar algo: para usar directamente los métodos y propiedades de la cadena (por ejemplo, ToLower) no puede escribir:

(string)o.ToLower(); // won't compile

solo puedes escribir:

((string)o).ToLower();

pero podrías escribir en su lugar:

(o as string).ToLower();

La asopción es más legible (al menos en mi opinión).

BornToCode
fuente
la construcción (o como cadena) .ToLower () anula el propósito del operador as. Esto arrojará una excepción de referencia nula cuando o no se puede convertir en cadena.
James
@james - ¿Pero quién dijo que el único propósito del operador as es lanzar una excepción si falla el lanzamiento? Si sabe que o es una cadena y solo desea escribir un código más limpio, puede usarlo en (o as string).ToLower()lugar de los múltiples corchetes confusos.
BornToCode
el propósito de as es todo lo contrario: no debería arrojar la excepción cuando falla el lanzamiento, debería devolver nulo. Digamos que su o es una cadena con un valor nulo, ¿qué sucederá entonces? Sugerencia: su llamada a ToLower fallará.
James
@james - Tienes razón, pero ¿qué pasa con los casos en que sé con certeza que no será nulo y solo necesito hacer el casting para el compilador para permitirme acceder a los métodos de ese objeto?
BornToCode
1
definitivamente puede hacerlo, pero no es exactamente la mejor práctica porque no desea confiar en la persona que llama o en los sistemas externos para asegurarse de que su valor no sea nulo. Si está utilizando C # 6, entonces ¿podría hacer (o como cadena)? Reducir().
James
3
string s = o as string; // 2

Se prefiere, ya que evita la penalización de rendimiento de doble lanzamiento.

Chris S
fuente
Hola Chris, el enlace que estaba en esta respuesta ahora es un 404 ... ¿No estoy seguro de si tienes un reemplazo que quieres poner en su lugar?
Matt
3

Parece que los dos son conceptualmente diferentes.

Fundición directa

Los tipos no tienen que estar estrictamente relacionados. Viene en todo tipo de sabores.

  • Fundición implícita / explícita personalizada: generalmente se crea un nuevo objeto.
  • Tipo de valor implícito: copiar sin perder información.
  • Tipo de valor explícito: la copia y la información pueden perderse.
  • Relación IS-A: Cambiar el tipo de referencia, de lo contrario arroja una excepción.
  • Mismo tipo: 'La fundición es redundante'.

Parece que el objeto se va a convertir en otra cosa.

Operador AS

Los tipos tienen una relación directa. Como en:

  • Tipos de referencia: relación IS-A Los objetos son siempre los mismos, solo cambia la referencia.
  • Tipos de valor: copia de boxeo y tipos anulables.

Parece que vas a manejar el objeto de una manera diferente.

Muestras e IL

    class TypeA
    {
        public int value;
    }

    class TypeB
    {
        public int number;

        public static explicit operator TypeB(TypeA v)
        {
            return new TypeB() { number = v.value };
        }
    }

    class TypeC : TypeB { }
    interface IFoo { }
    class TypeD : TypeA, IFoo { }

    void Run()
    {
        TypeA customTypeA = new TypeD() { value = 10 };
        long longValue = long.MaxValue;
        int intValue = int.MaxValue;

        // Casting 
        TypeB typeB = (TypeB)customTypeA; // custom explicit casting -- IL:  call class ConsoleApp1.Program/TypeB ConsoleApp1.Program/TypeB::op_Explicit(class ConsoleApp1.Program/TypeA)
        IFoo foo = (IFoo)customTypeA; // is-a reference -- IL: castclass  ConsoleApp1.Program/IFoo

        int loseValue = (int)longValue; // explicit -- IL: conv.i4
        long dontLose = intValue; // implict -- IL: conv.i8

        // AS 
        int? wraps = intValue as int?; // nullable wrapper -- IL:  call instance void valuetype [System.Runtime]System.Nullable`1<int32>::.ctor(!0)
        object o1 = intValue as object; // box -- IL: box [System.Runtime]System.Int32
        TypeD d1 = customTypeA as TypeD; // reference conversion -- IL: isinst ConsoleApp1.Program/TypeD
        IFoo f1 = customTypeA as IFoo; // reference conversion -- IL: isinst ConsoleApp1.Program/IFoo

        //TypeC d = customTypeA as TypeC; // wouldn't compile
    }
Lucas Teixeira
fuente
2

Me gustaría llamar la atención sobre los siguientes detalles del operador as :

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/as

Tenga en cuenta que el operador as solo realiza conversiones de referencia, conversiones anulables y conversiones de boxeo. El operador as no puede realizar otras conversiones, como las conversiones definidas por el usuario, que en su lugar deberían realizarse utilizando expresiones de conversión.

Vadim S.
fuente
0

Al intentar obtener la representación de cadena de cualquier cosa (de cualquier tipo) que podría ser nula, prefiero la siguiente línea de código. Es compacto, invoca ToString () y maneja correctamente los valores nulos. Si o es nulo, s contendrá String.Empty.

String s = String.Concat(o);
xtrem
fuente
0

Como nadie lo mencionó, la palabra clave más cercana a la instancia de Java es esta:

obj.GetType().IsInstanceOfType(otherObj)
Bennett Yeo
fuente
0

Utilice la conversión directa string s = (string) o;si en el contexto lógico de su aplicación stringes el único tipo válido. Con este enfoque, obtendrá InvalidCastExceptione implementará el principio de Fail-fast . Su lógica estará protegida para que no pase más el tipo no válido u obtenga NullReferenceException si se utiliza el asoperador.

Si la lógica espera varios tipos diferentes de conversión string s = o as string;y verifíquelo nullo use el isoperador.

Nueva característica fresca han aparecido en C # 7.0 para simplificar el elenco y el cheque es una comparación de patrones :

if(o is string s)
{
  // Use string variable s
}

or

switch (o)
{
  case int i:
     // Use int variable i
     break;
  case string s:
     // Use string variable s
     break;
 }
Dmitry
fuente
0

Las siguientes dos formas de conversión de tipos (conversión) se admiten en C #:

El |

(CV

• Convertir el tipo estático de v a c en la expresión dada

• Solo es posible si el tipo dinámico de v es c, o un subtipo de c

• Si no, se lanza una InvalidCastException

El |

v como C

• Variante no fatal de (c) v

• Por lo tanto, convierta el tipo estático de v a c en la expresión dada

• Devuelve nulo si el tipo dinámico de v no es c, o un subtipo de c


fuente