c# - para - seo html
Casting vs usando la palabra clave ''as'' en el CLR (18)
Además de todo lo que ya se expuso aquí, acabo de encontrar una diferencia práctica que creo que vale la pena destacar, entre el lanzamiento explícito
var x = (T) ...
versus usar el operador as
.
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;
}
}
Línea inferior: GenericCaster2 no funcionará con tipos de estructura. GenericCaster lo hará.
Cuando programo interfaces, descubrí que estoy haciendo un montón de conversión 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 costos 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?
Aquí hay otra respuesta, con alguna comparación de 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 mira la IL que produce cada método. Incluso si los códigos de operación no significan nada para usted, puede ver una diferencia importante: se está llamando a isinst seguido de castclass en el método DirectCast. Así que 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 contra el castclass
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 un poco)
- Al tener que realizar comprobaciones para asegurarse de 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 de 12% más lenta)
Personalmente siempre uso As, porque es fácil de leer y es recomendado por el equipo de desarrollo de .NET (o Jeffrey Richter de todos modos)
Depende, ¿quieres comprobar si hay un valor nulo después de usar "como" o preferirías que tu aplicación arrojara una excepción?
Mi regla de oro es si siempre espero que la variable sea del tipo que estoy esperando en el momento en que quiero usar una conversión. Si es posible que la variable no se convierta a lo que quiero y estoy preparado para manejar los nulos de usar como, usaré como.
Echa un vistazo a estos enlaces:
- http://gen5.info/q/2008/06/13/prefix-casting-versus-as-casting-in-c/
- http://www.codeproject.com/Articles/8052/Type-casting-impact-over-execution-performance-in
Te muestran algunos detalles y pruebas de rendimiento.
El operador as
solo se puede utilizar en tipos de referencia, no se puede sobrecargar y devolverá el null
si la operación falla. Nunca lanzará una excepción.
La conversió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, es una cuestión de si desea lanzar una excepción en una conversión fallida.
El problema del OP se limita a una situación específica de lanzamiento. El título abarca muchas más situaciones.
Aquí hay una visión general de todas las situaciones de casting relevantes 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
}
Esta no es una respuesta a la pregunta, pero comente el 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 por el tipo de objeto que obtiene.
Si emite 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 encasillados.
Ignore el consejo de Jon Skeet, re: evite el patrón de prueba y lanzamiento, 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 lanzamiento 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. Realicé algunas pruebas reales , y la prueba y el cast es en realidad más rápido que la comparación de cast-and-null, y es más seguro porque no tiene la posibilidad de tener una referencia nula fuera del alcance si el cast fallar.
Si desea una razón por la cual el test-and-cast es más rápido, o al menos no más lento, existe una razón simple y compleja.
Simple: incluso los compiladores ingenuos unirán dos operaciones similares, como prueba y conversión, en una sola prueba y rama. cast-and-null-test puede forzar dos pruebas y una bifurcación, una para la prueba de tipo y la conversión a nula en caso de fallo, una para la comprobación nula en sí misma. Como mínimo, ambos se optimizarán a una sola prueba y se ramificarán, por lo que la prueba y el lanzamiento no serían ni más lentos ni más rápidos que la prueba de conversión y anulación.
Complejo: por qué la prueba y el lanzamiento son más rápidos: la prueba de conversión y nula introduce otra variable en el ámbito externo que el compilador debe rastrear para el nivel de vida, y es posible que no pueda optimizar esa variable dependiendo de qué tan complejo sea su control. el flujo es. A la inversa, 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 alcance finalice, por lo que puede optimizar mejor la asignación de registros.
Así que por favor, POR FAVOR, deje que este consejo de "cast-and-null-test es mejor que test-and-cast". POR FAVOR. test-and-cast es más seguro y más rápido.
La palabra clave as
funciona igual que una conversión explícita entre tipos de referencia compatibles con la diferencia principal de que no genera una excepción si la conversión falla. Más bien, produce un valor nulo en la variable de destino. Dado que las excepciones son muy caras en términos de rendimiento, se considera un método de conversión mucho mejor.
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 operador como, ya que ahora puede escribir:
if (randomObject is TargetType tt)
{
// Use tt here
}
Tenga en cuenta que tt
todavía está dentro del alcance después de esto, pero no está asignado definitivamente. (Definitivamente se asigna dentro del cuerpo de if
). Eso es un poco molesto en algunos casos, por lo que si realmente te importa introducir el menor número posible de variables en todos los ámbitos, es posible que desees usarlo seguido de una conversión.
No creo que ninguna de las respuestas hasta el momento (al momento de iniciar esta respuesta) realmente haya explicado 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 }
No solo se está comprobando dos veces, sino que puede estar verificando cosas diferentes, si
randomObject
es un campo en lugar de una variable local. Es posible que el "si" pase pero luego larandomObject
falla, si otro hilo cambia el valor derandomObject
entre los dos.Si
randomObject
realmente debería ser una instancia deTargetType
, es decir, si no lo es, eso significa que hay un error, entonces lanzar es la solución correcta. Eso lanza una excepción inmediatamente, 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 deTargetType
yTargetType
es un tipo de referencia, entonces use un código como este:TargetType convertedRandomObject = randomObject as TargetType; if (convertedRandomObject != null) { // Do stuff with convertedRandomObject }
Si
randomObject
podría ser una instancia deTargetType
yTargetType
es un tipo de valor, entonces no podemos usarloas
mismoTargetType
, pero sí podemos usar un tipo que puedaTargetType
: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 operador es su amigo. En este caso, no importa si TargetType es un tipo de referencia o un tipo de valor.
Puede haber otros casos relacionados con genéricos donde
is
útil (porque puede que no sepa si T es un tipo de referencia o no, por lo que no puede usar) pero son relativamente poco claros.Casi seguro que he usado
is
para el caso del tipo de valor antes de ahora, no habiendo pensado en usar un tipo que pueda contener nulos y juntos :)
EDITAR: tenga en cuenta que ninguno de los anteriores se refiere al rendimiento, excepto en el caso del tipo de valor, en el que he notado que unboxing a un tipo de valor anulable es realmente más lento, pero consistente.
De acuerdo con la respuesta de naasking, tanto las pruebas JIT como las que se muestran a continuación son tan rápidas como las de los nodos de prueba de nulidad, como se muestra en el código a continuación:
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 portátil, todos estos se ejecutan en unos 60 ms. Dos cosas a tener en cuenta:
- No hay diferencia significativa entre ellos. (De hecho, hay situaciones en las que la comprobación de nulidad es definitivamente más lenta. El código anterior en realidad facilita la comprobació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 ellos son increíblemente rápidos. Esto simplemente no será el cuello de botella en su código a menos que luego no vaya a hacer nada con los valores.
Así que no nos preocupemos por el rendimiento. Vamos a preocuparnos por la corrección y la consistencia.
Sostengo que tanto el hecho de ser y el reparto (o el que es y el as) son inseguros cuando se trata de variables, ya que el tipo de valor al que se refiere puede cambiar debido a otro hilo entre la prueba y el reparto. Esa sería una situación bastante rara, pero preferiría tener una convención que pueda usar constantemente.
También sostengo que la comprobación de nulidad proporciona 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. El 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 hacen y están haciendo, aunque obviamente de una manera más barata.
Lo que elija depende en gran medida de lo que se requiere. Prefiero el casting explícito
IMyInterface = (IMyInterface)someobj;
porque si el objeto debe ser del tipo IMyInterface y no lo es, definitivamente es un problema. Es mejor obtener el error lo antes posible porque el error exacto se corregirá en lugar de corregir su efecto secundario.
Pero si trata con métodos que aceptan object
como parámetros, entonces debe verificar su tipo exacto antes de ejecutar cualquier código. En tal caso as
sería útil para evitar la InvalidCastException
.
Mi respuesta es solo sobre la velocidad en los casos en que no verificamos el tipo y no verificamos 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 enfocarte en la velocidad (como lo hice) porque todo esto es muy, muy rápido.
No es realmente una respuesta a tu pregunta, pero lo que creo que es un punto importante relacionado.
Si está programando para una interfaz que no debería necesitar lanzar. Esperemos que estos moldes sean muy raros. Si no, es probable que necesites repensar algunas de tus interfaces.
Si la conversión falla, la palabra clave ''as'' no lanza una excepción; establece la variable en nulo (o en su valor predeterminado para los tipos de valor) en su lugar.
Si usa los PIA de Office dirigidos a .NET Framework 4.X, debe usar la palabra clave as , 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;
El lanzamiento está bien cuando se dirige a .NET 2.0 aunque:
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 el miembro requerido del compilador ''Microsoft.CSharp.RuntimeBinder.Binder.Convert''
error CS0656: Falta el miembro requerido del compilador ''Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create''
Una de las diferencias más sutiles entre los dos es que la palabra clave "como" no se puede usar para lanzar cuando un operador de cast 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 "as" no tienen en cuenta a los operadores de conversión. La string cast = (string)f;
línea string cast = (string)f;
Aunque funciona bien.
como nunca lanza una excepción si no puede realizar la conversión devolviendo null en su lugar ( ya que opera solo en tipos de referencia). Así que usarlo es básicamente equivalente a
_myCls2 = _myObj is MyClass ? (MyClass)_myObj : null;
Los lanzamientos de estilo C, por otro lado, lanzan una excepción cuando no es posible la conversión.
"as" devolverá NULL si no es posible lanzar.
Casting antes planteará una excepción.
Para el rendimiento, elevar una excepción suele ser más costoso en el tiempo.