como - uso de enum en c#
C#conversión no boxeo de enum genérico a int? (7)
Dado un parámetro genérico TEnum que siempre será un tipo enum, ¿hay alguna forma de convertir TEnum a int sin boxing / unboxing?
Vea este código de ejemplo. Esto boxeará / desempaquetará el valor innecesariamente.
private int Foo<TEnum>(TEnum value)
where TEnum : struct // C# does not allow enum constraint
{
return (int) (ValueType) value;
}
El C # anterior es el modo de publicación compilado para los siguientes códigos de operación (anotar box y desempaquetar unboxing):
.method public hidebysig instance int32 Foo<valuetype
.ctor ([mscorlib]System.ValueType) TEnum>(!!TEnum ''value'') cil managed
{
.maxstack 8
IL_0000: ldarg.1
IL_0001: box !!TEnum
IL_0006: unbox.any [mscorlib]System.Int32
IL_000b: ret
}
La conversión de Enum ha sido tratada extensamente en SO, pero no pude encontrar una discusión abordando este caso específico.
No estoy seguro de que esto sea posible en C # sin usar Reflection.Emit. Si usa Reflection.Emit, puede cargar el valor de la enumeración en la pila y luego tratarlo como si fuera un int.
Sin embargo, tienes que escribir un montón de código, por lo que querrás comprobar si realmente obtendrás algún rendimiento al hacerlo.
Creo que el IL equivalente sería:
.method public hidebysig instance int32 Foo<valuetype
.ctor ([mscorlib]System.ValueType) TEnum>(!!TEnum ''value'') cil managed
{
.maxstack 8
IL_0000: ldarg.1
IL_000b: ret
}
Tenga en cuenta que esto fallaría si su enumeración deriva de long
(un entero de 64 bits).
EDITAR
Otro pensamiento sobre este enfoque. Reflection.Emit puede crear el método anterior, pero la única forma que tendría de vincularlo sería a través de una llamada virtual (es decir, implementa una interfaz / resumen conocido en tiempo de compilación que podría llamar) o una llamada indirecta (es decir, a través de una invocación de delegado). Imagino que ambos escenarios serían más lentos que la sobrecarga de boxeo / desempaquetado de todos modos.
Además, no olvide que el JIT no es tonto y puede encargarse de esto por usted. ( EDITAR vea el comentario de Eric Lippert sobre la pregunta original - dice que el jitter no realiza actualmente esta optimización ) .
Al igual que con todos los problemas relacionados con el rendimiento: medir, medir, medir!
Supongo que siempre puede usar System.Reflection.Emit para crear un método dinámico y emitir las instrucciones que lo hacen sin el boxeo, aunque podría no ser verificable.
... Incluso estoy ''más tarde'':)
pero solo para extender sobre la publicación anterior (Michael B), que hizo todo el trabajo interesante
y me interesó hacer una envoltura para un caso genérico (si realmente quieres enviar un genérico a enum)
... y optimizado un poco ... (nota: el punto principal es usar ''como'' en Func <> / delegates en su lugar - como Enum, los tipos de valor no lo permiten)
public static class Identity<TEnum, T>
{
public static readonly Func<T, TEnum> Cast = (Func<TEnum, TEnum>)((x) => x) as Func<T, TEnum>;
}
... y puedes usarlo así ...
enum FamilyRelation { None, Father, Mother, Brother, Sister, };
class FamilyMember
{
public FamilyRelation Relation { get; set; }
public FamilyMember(FamilyRelation relation)
{
this.Relation = relation;
}
}
class Program
{
static void Main(string[] args)
{
FamilyMember member = Create<FamilyMember, FamilyRelation>(FamilyRelation.Sister);
}
static T Create<T, P>(P value)
{
if (typeof(T).Equals(typeof(FamilyMember)) && typeof(P).Equals(typeof(FamilyRelation)))
{
FamilyRelation rel = Identity<FamilyRelation, P>.Cast(value);
return (T)(object)new FamilyMember(rel);
}
throw new NotImplementedException();
}
}
... para (int) - just (int) rel
Sé que llego muy tarde a la fiesta, pero si solo necesitas hacer un lanzamiento seguro como este, puedes usar lo siguiente usando Delegate.CreateDelegate
:
public static int Identity(int x){return x;}
// later on..
Func<int,int> identity = Identity;
Delegate.CreateDelegate(typeof(Func<int,TEnum>),identity.Method) as Func<int,TEnum>
ahora sin escribir árboles Reflection.Emit
o expression, tiene un método que convertirá int en enumeración sin boxeo o unboxing. Tenga en cuenta que TEnum
aquí debe tener un tipo subyacente de int
o esto arrojará una excepción diciendo que no se puede vincular.
Editar: Otro método que también funciona y podría ser un poco menos para escribir ...
Func<TEnum,int> converter = EqualityComparer<TEnum>.Default.GetHashCode;
Esto funciona para convertir su enum de 32 bits o menos de un TEnum a un int. No de la otra manera. En .Net 3.5+, EnumEqualityComparer
está optimizado para básicamente convertir esto en un valor de retorno (int)value
;
Usted está pagando los gastos generales de usar un delegado, pero sin duda será mejor que el boxeo.
Espero que no sea demasiado tarde ...
Creo que debes considerar resolver tu problema con un enfoque diferente en lugar de usar Enums para crear una clase con propiedades públicas de solo lectura.
si usa ese enfoque, tendrá un objeto que se "siente" como un Enum, pero tendrá toda la flexibilidad de una clase, lo que significa que puede anular cualquiera de los operadores.
hay otras ventajas, como hacer que esa clase sea parcial, lo que le permitirá definir la misma enumeración en más de un archivo / dll, lo que permite agregar valores a un dll común sin volver a compilarlo.
No pude encontrar ninguna buena razón para no tomar ese enfoque (esta clase se ubicará en el montón y no en la pila, que es más lenta pero vale la pena)
por favor dejame saber lo que tu piensas.
Aquí hay una manera más simple y rápida. (con poca restricción. :-))
public class BitConvert
{
[StructLayout(LayoutKind.Explicit)]
struct EnumUnion32<T> where T : struct {
[FieldOffset(0)]
public T Enum;
[FieldOffset(0)]
public int Int;
}
public static int Enum32ToInt<T>(T e) where T : struct {
var u = default(EnumUnion32<T>);
u.Enum = e;
return u.Int;
}
public static T IntToEnum32<T>(int value) where T : struct {
var u = default(EnumUnion32<T>);
u.Int = value;
return u.Enum;
}
}
Esto es similar a las respuestas publicadas aquí, pero usa árboles de expresión para emitir il para convertir entre tipos. Expression.Convert
hace el truco. El delegado compilado (caster) está almacenado en caché por una clase estática interna. Dado que el objeto fuente se puede deducir del argumento, supongo que ofrece una llamada más clara. Por ejemplo, un contexto genérico:
static int Generic<T>(T t)
{
int variable = -1;
// may be a type check - if(...
variable = CastTo<int>.From(t);
return variable;
}
La clase:
/// <summary>
/// Class to cast to type <see cref="T"/>
/// </summary>
/// <typeparam name="T">Target type</typeparam>
public static class CastTo<T>
{
/// <summary>
/// Casts <see cref="S"/> to <see cref="T"/>.
/// This does not cause boxing for value types.
/// Useful in generic methods.
/// </summary>
/// <typeparam name="S">Source type to cast from. Usually a generic type.</typeparam>
public static T From<S>(S s)
{
return Cache<S>.caster(s);
}
private static class Cache<S>
{
public static readonly Func<S, T> caster = Get();
private static Func<S, T> Get()
{
var p = Expression.Parameter(typeof(S));
var c = Expression.ConvertChecked(p, typeof(T));
return Expression.Lambda<Func<S, T>>(c, p).Compile();
}
}
}
Puede reemplazar la func de caster
con otras implementaciones. Voy a comparar el rendimiento de unos pocos:
direct object casting, ie, (T)(object)S
caster1 = (Func<T, T>)(x => x) as Func<S, T>;
caster2 = Delegate.CreateDelegate(typeof(Func<S, T>), ((Func<T, T>)(x => x)).Method) as Func<S, T>;
caster3 = my implementation above
caster4 = EmitConverter();
static Func<S, T> EmitConverter()
{
var method = new DynamicMethod(string.Empty, typeof(T), new[] { typeof(S) });
var il = method.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
if (typeof(S) != typeof(T))
{
il.Emit(OpCodes.Conv_R8);
}
il.Emit(OpCodes.Ret);
return (Func<S, T>)method.CreateDelegate(typeof(Func<S, T>));
}
Moldes en caja :
int
aint
fundición de objetos -> 42 ms
caster1 -> 102 ms
caster2 -> 102 ms
caster3 -> 90 ms
caster4 -> 101 msint
aint?
fundición de objetos -> 651 ms
caster1 -> falla
caster2 -> falla
caster3 -> 109 ms
caster4 -> fallaint?
aint
fundición de objetos -> 1957 ms
caster1 -> falla
caster2 -> falla
caster3 -> 124 ms
caster4 -> fallaenum
aint
fundición de objetos -> 405 ms
caster1 -> falla
caster2 -> 102 ms
caster3 -> 78 ms
caster4 -> fallaint
aenum
fundición de objetos -> 370 ms
caster1 -> falla
caster2 -> 93 ms
caster3 -> 87 ms
caster4 -> fallaint?
enum
fundición de objetos -> 2340 ms
caster1 -> falla
caster2 -> falla
caster3 -> 258 ms
caster4 -> fallaenum?
aint
fundición de objetos -> 2776 ms
caster1 -> falla
caster2 -> falla
caster3 -> 131 ms
caster4 -> falla
Expression.Convert
pone un elenco directo del tipo de fuente al tipo de destino, por lo que puede resolver los moldes explícitos e implícitos (sin mencionar los moldes de referencia). Así que esto da paso para manejar el casting que de otro modo solo es posible cuando no está encasillado (es decir, en un método genérico si lo haces (TTarget)(object)(TSource)
explotará si no es una conversión de identidad (como en la sección anterior) o conversión de referencia (como se muestra en la sección posterior)). Entonces los incluiré en las pruebas.
Moldes sin caja:
int
paradouble
objeto fundido -> error
caster1 -> falla
caster2 -> falla
caster3 -> 109 ms
caster4 -> 118 msenum
aint?
objeto fundido -> error
caster1 -> falla
caster2 -> falla
caster3 -> 93 ms
caster4 -> fallaint
aenum?
objeto fundido -> error
caster1 -> falla
caster2 -> falla
caster3 -> 93 ms
caster4 -> fallaenum?
aint?
objeto fundido -> error
caster1 -> falla
caster2 -> falla
caster3 -> 121 ms
caster4 -> fallaint?
aenum?
objeto fundido -> error
caster1 -> falla
caster2 -> falla
caster3 -> 120 ms
caster4 -> falla
Por el gusto de hacerlo, probé algunas conversiones de tipo de referencia:
PrintStringProperty
astring
(cambio de representación)objeto fundido -> error (bastante obvio, ya que no se devuelve al tipo original)
caster1 -> falla
caster2 -> falla
caster3 -> 315 ms
caster4 -> fallastring
aobject
(representación que conserva la conversión de referencia)fundición de objetos -> 78 ms
caster1 -> falla
caster2 -> falla
caster3 -> 322 ms
caster4 -> falla
Probado así:
static void TestMethod<T>(T t)
{
CastTo<int>.From(t); //computes delegate once and stored in a static variable
int value = 0;
var watch = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
value = (int)(object)t;
// similarly value = CastTo<int>.From(t);
// etc
}
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
}
Nota:
Mi estimación es que a menos que ejecutes esto al menos cien mil veces, no vale la pena, y no tienes casi nada de qué preocuparte sobre el boxeo. Tenga en cuenta que los delegados de almacenamiento en memoria caché tiene un golpe en la memoria. Pero más allá de ese límite, la mejora de la velocidad es significativa, especialmente cuando se trata de lanzar elementos nulables .
Pero la verdadera ventaja de la
CastTo<T>
es cuando permite conversiones que son posibles sin casillas, como(int)double
en un contexto genérico. Como tal(int)(object)double
falla(int)(object)double
veces en estos escenarios.He usado
Expression.ConvertChecked
lugar deExpression.Convert
para que se comprueben los desbordamientos aritméticos y los subdesbordamientos (es decir, los resultados en la excepción). Como il se genera durante el tiempo de ejecución, y las configuraciones comprobadas son una cosa del tiempo de compilación, no hay forma de que pueda conocer el contexto comprobado del código de llamada. Esto es algo que debes decidir tú mismo. Elija uno o proporcione sobrecarga para ambos (mejor).Si no existe un elenco desde
TSource
aTTarget
, se lanza una excepción mientras se compila el delegado. Si desea un comportamiento diferente, como obtener un valor predeterminado deTTarget
, puede verificar la compatibilidad de tipo mediante la reflexión antes de compilar delegado. Usted tiene el control total del código que se genera. Sin embargo, va a ser extremadamente complicado, debe verificar la compatibilidad de las referencias (IsSubClassOf
,IsAssignableFrom
), la existencia del operador de conversión (que va a ser hacky) e, incluso, algún tipo de convertibilidad incorporada entre los tipos primitivos. Va a ser extremadamente hacky. Más fácil es capturar excepciones y devolver el valor predeterminado delegado basado enConstantExpression
. Solo indicando la posibilidad de que pueda imitar el comportamiento de una palabra clave que no arroja. Es mejor mantenerse alejado de ella y apegarse a la convención.