Transmitir vs usar la palabra clave 'as' en el CLR

387

Al programar interfaces, descubrí que estoy haciendo muchas conversiones o conversión de tipo de objeto.

¿Hay alguna diferencia entre estos dos métodos de conversión? Si es así, ¿hay una diferencia de costo o cómo afecta esto a mi programa?

public interface IMyInterface
{
    void AMethod();
}

public class MyClass : IMyInterface
{
    public void AMethod()
    {
       //Do work
    }

    // Other helper methods....
}

public class Implementation
{
    IMyInterface _MyObj;
    MyClass _myCls1;
    MyClass _myCls2;

    public Implementation()
    {
        _MyObj = new MyClass();

        // What is the difference here:
        _myCls1 = (MyClass)_MyObj;
        _myCls2 = (_MyObj as MyClass);
    }
}

Además, ¿cuál es "en general" el método preferido?

Frank V
fuente
¿Podría agregar un pequeño ejemplo de por qué está usando los moldes en primer lugar a la pregunta, o tal vez comenzar uno nuevo? Me interesa un poco por qué necesitarías el elenco solo para pruebas unitarias. Sin embargo, creo que está fuera del alcance de esta pregunta.
Erik van Brakel el
2
Probablemente pueda cambiar la prueba de mi unidad para evitar esta necesidad. Básicamente se reduce al hecho de que tengo una propiedad en mi objeto concreto que no está en la interfaz. Necesito establecer esa propiedad, pero en la vida real esa propiedad se habría establecido por otros medios. Eso responde tu pregunta?
Frank V
Como Patrik Hagne señala astutamente a continuación, existe ES una diferencia.
Neil

Respuestas:

520

La respuesta debajo de la línea fue escrita en 2008.

C # 7 introdujo la coincidencia de patrones, que ha reemplazado en gran medida al asoperador, como ahora puede escribir:

if (randomObject is TargetType tt)
{
    // Use tt here
}

Tenga en cuenta que tttodavía está dentro del alcance después de esto, pero no está definitivamente asignado. ( Definitivamente está asignado dentro del ifcuerpo). Eso es un poco molesto en algunos casos, por lo que si realmente te importa introducir la menor cantidad de variables posibles en cada ámbito, es posible que aún quieras usar isseguido de un reparto.


No creo que ninguna de las respuestas hasta ahora (en el momento de comenzar esta respuesta) haya explicado realmente dónde vale la pena usar cuál.

  • No hagas esto:

    // Bad code - checks type twice for no reason
    if (randomObject is TargetType)
    {
        TargetType foo = (TargetType) randomObject;
        // Do something with foo
    }

    Esto no solo está comprobando dos veces, sino que puede estar comprobando cosas diferentes, si randomObjectes un campo en lugar de una variable local. Es posible que el "si" pase pero luego el lanzamiento falle, si otro hilo cambia el valor de randomObjectentre los dos.

  • Si randomObjectrealmente debería ser una instancia de TargetType, es decir, si no lo es, eso significa que hay un error, entonces lanzar es la solución correcta. Eso arroja una excepción de inmediato, lo que significa que no se realiza más trabajo bajo suposiciones incorrectas, y la excepción muestra correctamente el tipo de error.

    // This will throw an exception if randomObject is non-null and
    // refers to an object of an incompatible type. The cast is
    // the best code if that's the behaviour you want.
    TargetType convertedRandomObject = (TargetType) randomObject;
  • Si randomObject podría ser una instancia de TargetTypey TargetTypees un tipo de referencia, utilice un código como este:

    TargetType convertedRandomObject = randomObject as TargetType;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject
    }
  • Si randomObject podría ser una instancia de TargetTypey TargetTypees un tipo de valor, entonces no podemos usarlo asconsigo TargetTypemismo, pero podemos usar un tipo anulable:

    TargetType? convertedRandomObject = randomObject as TargetType?;
    if (convertedRandomObject != null)
    {
        // Do stuff with convertedRandomObject.Value
    }

    (Nota: actualmente esto es en realidad más lento que el reparto + . Creo que es más elegante y consistente, pero ahí vamos).

  • Si realmente no necesita el valor convertido, pero solo necesita saber si es una instancia de TargetType, entonces el isoperador es su amigo. En este caso, no importa si TargetType es un tipo de referencia o un tipo de valor.

  • Puede haber otros casos que involucren genéricos donde issea ​​útil (porque es posible que no sepa si T es un tipo de referencia o no, por lo que no puede usar como), pero son relativamente oscuros.

  • Es casi seguro que lo he usado antes ispara el caso del tipo de valor, sin haber pensado en usar un tipo anulable y asjuntos :)


EDITAR: tenga en cuenta que ninguno de los anteriores habla sobre el rendimiento, excepto el caso del tipo de valor, donde he notado que unboxing a un tipo de valor anulable es en realidad más lento, pero consistente.

De acuerdo con la respuesta de naasking, is-and-cast o is-and-as son tan rápidos como el chequeo nulo con los JIT modernos, como se muestra en el siguiente código:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i + 1] = "x";
            values[i + 2] = new object();
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);
    }

    static void FindLengthWithIsAndCast(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string) o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)        
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

En mi computadora portátil, todo esto se ejecuta en aproximadamente 60 ms. Dos cosas a tener en cuenta:

  • No hay diferencia significativa entre ellos. (De hecho, hay situaciones en las que la verificación as-plus-null definitivamente es más lenta. El código anterior en realidad facilita la verificación de tipo porque es para una clase sellada; si está buscando una interfaz, la balanza se inclina ligeramente a favor de as-plus-null-check.)
  • Todos son increíblemente rápidos. Esto simplemente no será el cuello de botella en su código a menos que realmente no vaya a hacer nada con los valores después.

Así que no nos preocupemos por el rendimiento. Preocupémonos por la corrección y la consistencia.

Sostengo que is-and-cast (o is-and-as) no son seguros cuando se trata de variables, ya que el tipo del valor al que se refiere puede cambiar debido a otro hilo entre la prueba y el elenco. Esa sería una situación bastante rara, pero prefiero tener una convención que pueda usar de manera consistente.

También sostengo que el cheque nulo como entonces da una mejor separación de las preocupaciones. Tenemos una declaración que intenta una conversión, y luego una declaración que usa el resultado. Is-and-cast o is-and-as realiza una prueba y luego otro intento de convertir el valor.

Para decirlo de otra manera, ¿alguien escribiría alguna vez :

int value;
if (int.TryParse(text, out value))
{
    value = int.Parse(text);
    // Use value
}

Eso es algo de lo que está haciendo el reparto, aunque obviamente de una manera bastante más barata.

Jon Skeet
fuente
77
Aquí está el costo de is / as / casting en términos de IL: atalasoft.com/cs/blogs/stevehawley/archive/2009/01/30/…
zócalo
3
En el caso, si targetObject podría ser un tipo de objetivo, ¿por qué el uso de "is" y la combinación de lanzamiento se consideran una mala práctica? Quiero decir, genera un código más lento, pero en este caso las intenciones son más claras que la conversión AS, como "Hacer algo si targetObject es targetType", en lugar de "Hacer algo si targetObject es nulo", además, la cláusula AS creará una variable innecesaria fuera del alcance de IF.
Valera Kolupaev
2
@Valera: Buenos puntos, aunque sugeriría que la prueba as / null es lo suficientemente idiomática como para que la intención sea clara para casi todos los desarrolladores de C #. No me gusta la duplicación involucrada en el reparto is +, personalmente. En realidad, me gustaría una especie de construcción "como si" que realizara ambas acciones en una. Van juntos tan a menudo ...
Jon Skeet
2
@ Jon Skeet: perdón por mi retraso. Is And Cast: 2135, Is And As: 2145, As And null check: 1961, specs: OS: Windows Seven, CPU: i5-520M, 4GB of DDR3 1033 ram, benchmark on array de 128,000,000 artículos.
Behrooz
2
Con C # 7 puede hacer: if (randomObject is TargetType convertedRandomObject){ // Do stuff with convertedRandomObject.Value}o usar switch/ case ver documentos
WerWet
72

"as" devolverá NULL si no es posible lanzarlo.

lanzar antes generará una excepción.

Para el rendimiento, generar una excepción suele ser más costoso en el tiempo.

Patrick Desjardins
fuente
3
El aumento de excepciones es más costoso, pero si sabe que el objeto puede lanzarse correctamente, ya que requiere más tiempo debido al control de seguridad (consulte la respuesta de Anton). Sin embargo, el costo del control de seguridad es, creo, bastante pequeño.
17
El costo de generar una excepción es un factor a considerar, pero a menudo es el diseño correcto.
Jeffrey L Whitledge
@panesofglass: para los tipos de referencia, la compatibilidad de conversión siempre se verificará en tiempo de ejecución tanto para as como para conversión, por lo que ese factor no distinguirá entre las dos opciones. (Si esto no fuera así, entonces el elenco no podría plantear una excepción.)
Jeffrey L Whitledge el
44
@Frank: si se requiere que use una colección pre-genérica, por ejemplo, y un método en su API requiere una lista de Empleados, y algún bromista en su lugar pasa una lista de Productos, entonces una excepción de lanzamiento no válida puede ser apropiada para indicar La violación de los requisitos de la interfaz.
Jeffrey L Whitledge
27

Aquí hay otra respuesta, con alguna comparación IL. Considera la clase:

public class MyClass
{
    public static void Main()
    {
        // Call the 2 methods
    }

    public void DirectCast(Object obj)
    {
        if ( obj is MyClass)
        { 
            MyClass myclass = (MyClass) obj; 
            Console.WriteLine(obj);
        } 
    } 


    public void UsesAs(object obj) 
    { 
        MyClass myclass = obj as MyClass; 
        if (myclass != null) 
        { 
            Console.WriteLine(obj);
        } 
    }
}

Ahora mire la IL que produce cada método. Incluso si los códigos operativos no significan nada para usted, puede ver una diferencia importante: isinst se llama seguido de castclass en el método DirectCast. Entonces, dos llamadas en lugar de una básicamente.

.method public hidebysig instance void  DirectCast(object obj) cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  brfalse.s  IL_0015
  IL_0008:  ldarg.1
  IL_0009:  castclass  MyClass
  IL_000e:  pop
  IL_000f:  ldarg.1
  IL_0010:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0015:  ret
} // end of method MyClass::DirectCast

.method public hidebysig instance void  UsesAs(object obj) cil managed
{
  // Code size       17 (0x11)
  .maxstack  1
  .locals init (class MyClass V_0)
  IL_0000:  ldarg.1
  IL_0001:  isinst     MyClass
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  brfalse.s  IL_0010
  IL_000a:  ldarg.1
  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0010:  ret
} // end of method MyClass::UsesAs

La palabra clave isinst versus la clase de cast

Esta publicación de blog tiene una comparación decente entre las dos formas de hacerlo. Su resumen es:

  • En una comparación directa, isinst es más rápido que castclass (aunque solo ligeramente)
  • Al tener que realizar verificaciones para asegurar que la conversión fue exitosa, isinst fue significativamente más rápido que castclass
  • No se debe usar una combinación de isinst y castclass, ya que esto fue mucho más lento que la conversión "segura" más rápida (más del 12% más lenta)

Personalmente siempre uso As, porque es fácil de leer y lo recomienda el equipo de desarrollo de .NET (o Jeffrey Richter de todos modos)

Chris S
fuente
Estaba buscando una explicación clara para el casting vs as, esta respuesta lo hace mucho más claro, ya que implica una explicación paso a paso del lenguaje intermedio común. ¡Gracias!
Morse
18

Una de las diferencias más sutiles entre los dos es que la palabra clave "as" no se puede usar para emitir cuando un operador de conversión está involucrado:

public class Foo
{
    public string Value;

    public static explicit operator string(Foo f)
    {
        return f.Value;
    }

}

public class Example
{
    public void Convert()
    {
        var f = new Foo();
        f.Value = "abc";

        string cast = (string)f;
        string tryCast = f as string;
    }
}

Esto no se compilará (aunque creo que lo hizo en versiones anteriores) en la última línea, ya que las palabras clave "como" no tienen en cuenta los operadores de conversión. Sin string cast = (string)f;embargo, la línea funciona bien.

Patrik Hägne
fuente
12

como nunca arroja una excepción si no puede realizar la conversión devolviendo nulo en su lugar ( ya que opera solo en tipos de referencia). Entonces, usar as es básicamente equivalente a

_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;

Los lanzamientos de estilo C, por otro lado, arrojan una excepción cuando no es posible la conversión.

Anton Gogolev
fuente
44
Equivalente, sí, pero no igual. Esto genera mucho más código que como.
zócalo
10

No es realmente una respuesta a su pregunta, pero creo que es un punto relacionado importante.

Si está programando para una interfaz, no debería necesitar emitir. Esperemos que estos moldes sean muy raros. Si no, es probable que deba repensar algunas de sus interfaces.

sapo
fuente
El casting, hasta ahora, se ha necesitado principalmente para mis Pruebas de Unidad, pero gracias por mencionarlo. Lo tendré en mente mientras trabajo en esto.
Frank V
Estoy de acuerdo con el sapo, también tengo curiosidad de por qué el aspecto de prueba de la unidad es relevante para el lanzamiento para ti @Frank V. Cuando hay necesidad de lanzar, a menudo hay una necesidad de rediseño o refactorización, ya que sugiere que estás intentando para calzar diferentes problemas donde se deben manejar de manera diferente.
El senador
@TheSenator Esta pregunta tiene más de 3 años, así que realmente no me acuerdo. Pero probablemente estaba usando agresivamente las interfaces incluso cuando se realizaban pruebas unitarias. Posiblemente porque estaba usando el patrón de fábrica y no tenía acceso a un constructor público en los objetos de destino para probar.
Frank V
9

Ignore el consejo de Jon Skeet, re: evite el patrón de prueba y reparto, es decir:

if (randomObject is TargetType)
{
    TargetType foo = randomObject as TargetType;
    // Do something with foo
}

La idea de que esto cuesta más que un reparto y una prueba nula es un MITO :

TargetType convertedRandomObject = randomObject as TargetType;
if (convertedRandomObject != null)
{
    // Do stuff with convertedRandomObject
}

Es una microoptimización que no funciona. Hice algunas pruebas reales , y test-and-cast es en realidad más rápido que la comparación de lanzamiento y nulo, y también es más seguro porque no tienes la posibilidad de tener una referencia nula en el ámbito fuera del if debería el lanzamiento fallar.

Si desea una razón por la cual la prueba y el lanzamiento es más rápida, o al menos no más lenta, hay una razón simple y compleja.

Simple: incluso los compiladores ingenuos fusionarán dos operaciones similares, como test-and-cast, en una sola prueba y rama. cast-and-null-test puede forzar dos pruebas y una rama, una para la prueba de tipo y conversión a nulo en caso de falla, una para la verificación nula en sí. Como mínimo, ambos se optimizarán para una sola prueba y ramificación, por lo que test-and-cast no sería ni más lento ni más rápido que cast-and-null-test.

Complejo: por qué test-and-cast es más rápido: cast-and-null-test introduce otra variable en el ámbito externo que el compilador debe rastrear para determinar la vida, y es posible que no pueda optimizar esa variable dependiendo de cuán complejo sea su control. el flujo es Por el contrario, test-and-cast introduce una nueva variable solo en un ámbito delimitado para que el compilador sepa que la variable está muerta después de que el ámbito finalice, y así puede optimizar mejor la asignación de registros.

Así que por favor, POR FAVOR, deje que este consejo de "probar y anular prueba sea mejor que probar y emitir" muera. POR FAVOR. test-and-cast es más seguro y más rápido.

naasking
fuente
77
@naasking: si prueba dos veces (según su primer fragmento), existe la posibilidad de que el tipo cambie entre las dos pruebas, si es un campo o refparámetro. Es seguro para las variables locales, pero no para los campos. Me interesaría ejecutar sus puntos de referencia, pero el código que ha proporcionado en su publicación de blog no está completo. Estoy de acuerdo con no micro-optimizar, pero no creo que usar el valor dos veces sea más legible o elegante que usar "como" y una prueba de nulidad. (Definitivamente usaría un elenco directo en lugar de "como" después de un es, por cierto.)
Jon Skeet
55
Tampoco veo por qué es más seguro. He demostrado por qué es menos seguro, de hecho. Claro, terminas con una variable de alcance que puede ser nula, pero a menos que comiences a usarla fuera del alcance del siguiente bloque "if", estás bien. La preocupación de seguridad que he planteado (alrededor de los campos que cambian su valor) es una preocupación genuina con el código que se muestra : su preocupación de seguridad requiere que los desarrolladores sean laxos en otro código.
Jon Skeet
1
+1 por señalar que es / cast o as / cast no es más lento en realidad, eso sí. Después de haber realizado una prueba completa yo mismo, puedo confirmar que no hace ninguna diferencia hasta donde puedo ver, y francamente, puede ejecutar una cantidad asombrosa de lanzamientos en muy poco tiempo. Actualizaré mi respuesta con el código completo.
Jon Skeet
1
De hecho, si el enlace no es local, existe la posibilidad de un error TOCTTOU (tiempo de verificación a tiempo de uso), entonces un buen punto allí. En cuanto a por qué es más seguro, trabajo con muchos desarrolladores junior que les gusta reutilizar a los locales por alguna razón. cast-and-null es, por lo tanto, un peligro muy real en mi experiencia, y nunca me he encontrado con una situación TOCTTOU ya que no diseño mi código de esa manera. En cuanto a la velocidad de prueba de tiempo de ejecución, ¡es incluso más rápido que el envío virtual [1]! Re: código, veré si puedo encontrar la fuente para la prueba de lanzamiento. [1] higherlogics.blogspot.com/2008/10/…
naasking
1
@naasking: nunca me he encontrado con el problema de reutilización local, pero diría que es más fácil detectar en la revisión de código que el error TOCTTOU más sutil. También vale la pena señalar que acabo de volver a ejecutar mi propia prueba de referencia para las interfaces en lugar de una clase sellada, y eso inclina el rendimiento a favor de una verificación nula ... pero como he dicho, el rendimiento no es Es por eso que elegiría cualquier enfoque particular aquí.
Jon Skeet
4

Si el reparto falla, la palabra clave 'as' no arroja una excepción; establece la variable en nulo (o en su valor predeterminado para los tipos de valor) en su lugar.

El Pitufo
fuente
3
No hay valores predeterminados para los tipos de valor. Como no se puede usar para emitir tipos de valor.
Patrik Hägne
2
La palabra clave 'as' no funciona realmente en los tipos de valor, por lo que siempre se establece en nulo.
Erik van Brakel el
4

Esta no es una respuesta a la pregunta, sino un comentario al ejemplo de código de la pregunta:

Por lo general, no debería tener que lanzar un objeto desde, por ejemplo, IMyInterface a MyClass. Lo mejor de las interfaces es que si toma un objeto como entrada que implementa una interfaz, no tiene que preocuparse de qué tipo de objeto está obteniendo.

Si lanza IMyInterface a MyClass, entonces ya asume que obtiene un objeto de tipo MyClass y no tiene sentido usar IMyInterface, porque si alimenta su código con otras clases que implementan IMyInterface, rompería su código ...

Ahora, mi consejo: si sus interfaces están bien diseñadas, puede evitar muchos tipos de letra.

f3lix
fuente
3

El asoperador solo se puede usar en tipos de referencia, no se puede sobrecargar y volverá nullsi la operación falla. Nunca arrojará una excepción.

La transmisión se puede usar en cualquier tipo compatible, se puede sobrecargar y generará una excepción si la operación falla.

La elección de cuál usar depende de las circunstancias. Principalmente, se trata de si desea lanzar una excepción en una conversión fallida.

Jeffrey L Whitledge
fuente
1
'as' también se puede usar en tipos de valores anulables, lo que proporciona un patrón interesante. Vea mi respuesta para el código.
Jon Skeet
1

Mi respuesta es solo sobre la velocidad en los casos en que no verificamos el tipo y no verificamos los valores nulos después del lanzamiento. Agregué dos pruebas adicionales al código de Jon Skeet:

using System;
using System.Diagnostics;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];

        for (int i = 0; i < Size; i++)
        {
            values[i] = "x";
        }
        FindLengthWithIsAndCast(values);
        FindLengthWithIsAndAs(values);
        FindLengthWithAsAndNullCheck(values);

        FindLengthWithCast(values);
        FindLengthWithAs(values);

        Console.ReadLine();
    }

    static void FindLengthWithIsAndCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = (string)o;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithIsAndAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            if (o is string)
            {
                string a = o as string;
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("Is and As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAsAndNullCheck(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            if (a != null)
            {
                len += a.Length;
            }
        }
        sw.Stop();
        Console.WriteLine("As and null check: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
    static void FindLengthWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = (string)o;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindLengthWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int len = 0;
        foreach (object o in values)
        {
            string a = o as string;
            len += a.Length;
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", len,
                          (long)sw.ElapsedMilliseconds);
    }
}

Resultado:

Is and Cast: 30000000 : 88
Is and As: 30000000 : 93
As and null check: 30000000 : 56
Cast: 30000000 : 66
As: 30000000 : 46

No intentes concentrarte en la velocidad (como lo hice) porque todo esto es muy, muy rápido.

CoperNick
fuente
Del mismo modo, en mis pruebas, descubrí que la asconversión (sin la comprobación de errores) se ejecutó aproximadamente un 1-3% más rápido que la conversión (alrededor de 540 ms frente a 550 ms en 100 millones de iteraciones). Ninguno de los dos hará o interrumpirá su solicitud.
palswim
1

Además de todo lo que ya se expuso aquí, acabo de encontrar una diferencia práctica que creo que vale la pena señalar, entre el casting explícito

var x = (T) ...

versus usar el asoperador.

Aquí está el ejemplo:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(GenericCaster<string>(12345));
        Console.WriteLine(GenericCaster<object>(new { a = 100, b = "string" }) ?? "null");
        Console.WriteLine(GenericCaster<double>(20.4));

        //prints:
        //12345
        //null
        //20.4

        Console.WriteLine(GenericCaster2<string>(12345));
        Console.WriteLine(GenericCaster2<object>(new { a = 100, b = "string" }) ?? "null");

        //will not compile -> 20.4 does not comply due to the type constraint "T : class"
        //Console.WriteLine(GenericCaster2<double>(20.4));
    }

    static T GenericCaster<T>(object value, T defaultValue = default(T))
    {
        T castedValue;
        try
        {
            castedValue = (T) Convert.ChangeType(value, typeof(T));
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }

    static T GenericCaster2<T>(object value, T defaultValue = default(T)) where T : class
    {
        T castedValue;
        try
        {
            castedValue = Convert.ChangeType(value, typeof(T)) as T;
        }
        catch (Exception)
        {
            castedValue = defaultValue;
        }

        return castedValue;
    }
}

En pocas palabras : GenericCaster2 no funcionará con tipos de estructura. GenericCaster lo hará.

Veverke
fuente
1

Si usa los PIA de Office dirigidos a .NET Framework 4.X, debe usar la palabra clave como , de lo contrario, no se compilará.

Microsoft.Office.Interop.Outlook.Application o = new Microsoft.Office.Interop.Outlook.Application();
Microsoft.Office.Interop.Outlook.MailItem m = o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem) as Microsoft.Office.Interop.Outlook.MailItem;

Sin embargo, la transmisión está bien cuando se dirige a .NET 2.0:

Microsoft.Office.Interop.Outlook.MailItem m = (Microsoft.Office.Interop.Outlook.MailItem)o.CreateItem(Microsoft.Office.Interop.Outlook.OlItemType.olMailItem);

Al apuntar a .NET 4.X los errores son:

error CS0656: Falta compilador miembro requerido 'Microsoft.CSharp.RuntimeBinder.Binder.Convert'

error CS0656: falta compilador miembro requerido 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'

Olivier MATROT
fuente
0

La aspalabra clave funciona igual que una conversión explícita entre tipos de referencia compatibles con la gran diferencia de que no genera una excepción si la conversión falla. Más bien, produce un valor nulo en la variable objetivo. Dado que las Excepciones son muy caras en términos de rendimiento, se considera un método de lanzamiento mucho mejor.

Cerebro
fuente
No es lo mismo, como uno llama a CastClass y el otro llama a IsInst en el código IL.
Jenix
0

Lo que elija depende en gran medida de lo que requiera. Prefiero casting explícito

IMyInterface = (IMyInterface)someobj;

porque si el objeto debería ser del tipo IMyInterface y no lo es, definitivamente es un problema. Es mejor obtener el error lo antes posible porque se solucionará el error exacto en lugar de corregir su efecto secundario.

Pero si maneja métodos que aceptan objectcomo parámetro, entonces debe verificar su tipo exacto antes de ejecutar cualquier código. En tal caso assería útil para que puedas evitarlo InvalidCastException.

Oleg
fuente
0

Depende, ¿desea verificar si es nulo después de usar "como" o prefiere que su aplicación arroje una excepción?

Mi regla general es que si siempre espero que la variable sea del tipo que estoy esperando en el momento en que quiero, uso un yeso. Si es posible que la variable no se convierta en lo que quiero y estoy preparado para manejar los valores nulos de usar as, usaré as.

Darryl Braaten
fuente
0

El problema del OP se limita a una situación de lanzamiento específica. El título cubre muchas más situaciones.
Aquí hay una descripción general de todas las situaciones de casting relevantes en las que actualmente puedo pensar:

private class CBase
{
}

private class CInherited : CBase
{
}

private enum EnumTest
{
  zero,
  one,
  two
}

private static void Main (string[] args)
{
  //########## classes ##########
  // object creation, implicit cast to object
  object oBase = new CBase ();
  object oInherited = new CInherited ();

  CBase oBase2 = null;
  CInherited oInherited2 = null;
  bool bCanCast = false;

  // explicit cast using "()"
  oBase2 = (CBase)oBase;    // works
  oBase2 = (CBase)oInherited;    // works
  //oInherited2 = (CInherited)oBase;   System.InvalidCastException
  oInherited2 = (CInherited)oInherited;    // works

  // explicit cast using "as"
  oBase2 = oBase as CBase;
  oBase2 = oInherited as CBase;
  oInherited2 = oBase as CInherited;  // returns null, equals C++/CLI "dynamic_cast"
  oInherited2 = oInherited as CInherited;

  // testing with Type.IsAssignableFrom(), results (of course) equal the results of the cast operations
  bCanCast = typeof (CBase).IsAssignableFrom (oBase.GetType ());    // true
  bCanCast = typeof (CBase).IsAssignableFrom (oInherited.GetType ());    // true
  bCanCast = typeof (CInherited).IsAssignableFrom (oBase.GetType ());    // false
  bCanCast = typeof (CInherited).IsAssignableFrom (oInherited.GetType ());    // true

  //########## value types ##########
  int iValue = 2;
  double dValue = 1.1;
  EnumTest enValue = EnumTest.two;

  // implicit cast, explicit cast using "()"
  int iValue2 = iValue;   // no cast
  double dValue2 = iValue;  // implicit conversion
  EnumTest enValue2 = (EnumTest)iValue;  // conversion by explicit cast. underlying type of EnumTest is int, but explicit cast needed (error CS0266: Cannot implicitly convert type 'int' to 'test01.Program.EnumTest')

  iValue2 = (int)dValue;   // conversion by explicit cast. implicit cast not possible (error CS0266: Cannot implicitly convert type 'double' to 'int')
  dValue2 = dValue;
  enValue2 = (EnumTest)dValue;  // underlying type is int, so "1.1" beomces "1" and then "one"

  iValue2 = (int)enValue;
  dValue2 = (double)enValue;
  enValue2 = enValue;   // no cast

  // explicit cast using "as"
  // iValue2 = iValue as int;   error CS0077: The as operator must be used with a reference type or nullable type
}
Tobias Knauss
fuente