2010-07-15 12 views
22

Mi aplicación tiene muchos valores de búsqueda diferentes, estos valores no cambian nunca, p. Ej. Estados Unidos. En lugar de ponerlos en las tablas de la base de datos, me gustaría usar enumeraciones.Enum y rendimiento

Pero, me doy cuenta que hacerlo de esta manera implica tener algunas enumeraciones y mucho casting de "int" y "cadena" hacia y desde mis enums.

Alternativa, veo a alguien mencionado usando un Dictionary <> como tablas de búsqueda, pero la implementación de enum parece ser más clara.

Entonces, me gustaría preguntar si mantener y pasar un montón de enumeraciones y convertirlas en un problema para el rendimiento o debería utilizar el enfoque de tablas de búsqueda, que funciona mejor?

Editar: La fundición es necesaria como ID para almacenarse en otras tablas de la base de datos.

+3

¿Por qué necesitará echarlas? – LukeH

+2

@LukeH: Presumiblemente porque en la base de datos los valores serán enteros. –

+0

@Jon: Eso es lo que supuse, pero sería bueno obtener aclaraciones del OP. – LukeH

Respuesta

29

Colada de int a una enumeración es extremadamente barato ... que va a ser más rápido que una búsqueda de diccionario. Básicamente es un no-op, solo copia los bits en una ubicación con un tipo nocional diferente.

Analizar una cadena en un valor enum será un poco más lento.

Dudo que esto sea un cuello de botella para usted sin embargo lo hace, para ser honesto ... sin saber más acerca de lo que está haciendo, es algo difícil de recomendar más allá de lo normal "escriba el más simple , código legible y fácil de mantener que funcionará, luego verifique que funcione lo suficientemente bien ".

+0

Esta podría ser otra pregunta en conjunto, pero relacionada: ¿se conectan las enumeraciones entre sí con los valores 'int' subyacentes coincidentes tan baratos como el molde' (int) '? –

+1

@CADBloke: lo esperaría, sí. Pero inténtalo si es importante para ti :) –

13

No notará una gran diferencia en el rendimiento entre los dos, pero aún así recomendaría usar un diccionario porque le dará un poco más de flexibilidad en el futuro.

Por un lado, un Enum en C# no puede tener automáticamente una clase asociada como en Java, por lo que si desea asociar información adicional con un estado (Nombre completo, Capital, Abreviatura postal, etc.) , la creación de una clase UnitedState facilitará el empaquetado de toda esa información en una sola colección.

Además, a pesar de que piensa en este valor nunca cambiará, no es perfectamente inmutable. Posiblemente podría tener un nuevo requisito para incluir Territorios, por ejemplo. O quizás deba permitir que los usuarios canadienses vean los nombres de las provincias canadienses en su lugar. Si trata esta colección como cualquier otra colección de datos (usando un repositorio para recuperar valores de ella), más adelante tendrá la opción de cambiar su implementación de repositorio para extraer valores de una fuente diferente (Base de datos, Servicio web, Sesión, etc.) Los mensajes son mucho menos versátiles.

Editar

En cuanto al argumento de rendimiento: Tenga en cuenta que no estás solo emitan un Enum de un int: también se está ejecutando ToString() en esa enumeración, lo que añade un considerable tiempo de procesamiento. Considere la siguiente prueba:

const int C = 10000; 
int[] ids = new int[C]; 
string[] names = new string[C]; 
Stopwatch sw = new Stopwatch(); 
sw.Start(); 
for (int i = 0; i< C; i++) 
{ 
    var id = (i % 50) + 1; 
    names[i] = ((States)id).ToString(); 
} 
sw.Stop(); 
Console.WriteLine("Enum: " + sw.Elapsed.TotalMilliseconds); 
var namesById = Enum.GetValues(typeof(States)).Cast<States>() 
       .ToDictionary(s => (int) s, s => s.ToString()); 
sw.Restart(); 
for (int i = 0; i< C; i++) 
{ 
    var id = (i % 50) + 1; 
    names[i] = namesById[id]; 
} 
sw.Stop(); 
Console.WriteLine("Dictionary: " + sw.Elapsed.TotalMilliseconds); 

Resultados:

Enum: 26.4875 
Dictionary: 0.7684 

Así que si realmente el rendimiento es su principal preocupación, un diccionario es definitivamente el camino a seguir. Sin embargo, estamos hablando de tiempos tan rápidos aquí que hay media docena de otras preocupaciones que abordaría antes de preocuparme por el problema de la velocidad.

Las enumeraciones en C# no se diseñaron para proporcionar asignaciones entre valores y cadenas. Fueron diseñados para proporcionar valores constantes fuertemente tipados que puede pasar en el código. Las dos principales ventajas de esto son:

  1. Usted tiene una pista compilador comprobado extra para ayudar a evitar el paso de argumentos en el orden equivocado, etc.
  2. en lugar de poner "mágicas" valores numéricos (por ejemplo, "42 ") en su código, puede decir" States.Oklahoma ", que hace que su código sea más legible.

diferencia de Java, C# no comprueba automáticamente los valores de elenco para asegurarse de que son válidos (myState = (States)321), por lo que no reciben ninguna verificación de datos durante la ejecución de los insumos sin hacerlo a mano. Si no tiene un código que se refiera explícitamente a los estados ("States.Oklahoma"), entonces no obtiene ningún valor del # 2 anterior. Eso nos deja con el # 1 como la única razón real para usar enumeraciones. Si este es un buen motivo para usted, le sugiero que utilice las enumeraciones en lugar de los puntos como valores clave. Luego, cuando necesite una cadena u otro valor relacionado con el estado, realice una búsqueda en el diccionario.

Así es como yo lo haría:

public enum StateKey{ 
    AL = 1,AK,AS,AZ,AR,CA,CO,CT,DE,DC,FM,FL,GA,GU, 
    HI,ID,IL,IN,IA,KS,KY,LA,ME,MH,MD,MA,MI,MN,MS, 
    MO,MT,NE,NV,NH,NJ,NM,NY,NC,ND,MP,OH,OK,OR,PW, 
    PA,PR,RI,SC,SD,TN,TX,UT,VT,VI,VA,WA,WV,WI,WY, 
} 

public class State 
{ 
    public StateKey Key {get;set;} 
    public int IntKey {get {return (int)Key;}} 
    public string PostalAbbreviation {get;set;} 

} 

public interface IStateRepository 
{ 
    State GetByKey(StateKey key); 
} 

public class StateRepository : IStateRepository 
{ 
    private static Dictionary<StateKey, State> _statesByKey; 
    static StateRepository() 
    { 
     _statesByKey = Enum.GetValues(typeof(StateKey)) 
     .Cast<StateKey>() 
     .ToDictionary(k => k, k => new State {Key = k, PostalAbbreviation = k.ToString()}); 
    } 
    public State GetByKey(StateKey key) 
    { 
     return _statesByKey[key]; 
    } 
} 

public class Foo 
{ 
    IStateRepository _repository; 
    // Dependency Injection makes this class unit-testable 
    public Foo(IStateRepository repository) 
    { 
     _repository = repository; 
    } 
    // If you haven't learned the wonders of DI, do this: 
    public Foo() 
    { 
     _repository = new StateRepository(); 
    } 

    public void DoSomethingWithAState(StateKey key) 
    { 
     Console.WriteLine(_repository.GetByKey(key).PostalAbbreviation); 
    } 
} 

esta manera:

  1. se llega a pasar en torno a valores de tipo fuerte que representan un estado,
  2. su búsqueda obtiene fail comportamiento rápido si se le da una entrada no válida,
  3. puede cambiar fácilmente dónde residen los datos de estado reales en el futuro,
  4. puede agregar fácilmente datos relacionados con el estado a la clase State en el futuro,
  5. puede agregar fácilmente nuevos estados, territorios, distritos, provincias o cualquier otra cosa en el futuro.
  6. obtener un nombre de un int es todavía aproximadamente 15 veces más rápido que cuando se usa Enum.ToString().

[gruñido]

+1

+1, estoy completamente de acuerdo con la parte "no perfectamente inmutable". Lo odiarás si tu aplicación de diez años se basa en un modelo político del pasado y ya no tienes soporte para eso ... –

+1

No estoy de acuerdo con que no haya una gran diferencia de rendimiento; las enumeraciones en C# son solo entradas, por lo que es mucho más rápido lanzar entre ellas y las entradas. Sus puntos son buenos, pero sería difícil recomendar sacrificar el rendimiento por una flexibilidad posiblemente innecesaria. – TMN

+2

@TMN: incluso si las enumeraciones fueran más rápidas, estamos hablando de velocidades tan altas que no se notarían en la gran mayoría de las aplicaciones. Sería difícil recomendar sacrificar flexibilidad para una mejora minúscula en el rendimiento. Sin embargo, como resultado, una búsqueda en el diccionario será mucho más rápida para el tipo de cosas que él está hablando. Ver mi respuesta actualizada. – StriplingWarrior

0

Enumerará mucho mejor que cualquier cosa, especialmente en el diccionario. Los mensajes solo usan un solo byte. ¿Pero por qué estarías lanzando? Parece que deberías estar usando las enumeraciones en todas partes.

+5

Enum usualmente usará 4 bytes; su tipo subyacente está predeterminado en 'int' a menos que declares explícitamente lo contrario. – LukeH

+1

La parte "supera en gran medida" depende en gran medida de lo que está haciendo con la enumeración una vez que la obtiene. Si lo usa en una instrucción switch, las enumeraciones serán más rápidas. Si llama a ToString en él, un diccionario rellenado previamente será más rápido. Ver mi respuesta – StriplingWarrior

-2

Evite enum todo lo que pueda: las enumeraciones deben reemplazarse por singleton derivadas de la clase base o la implementación de una interfaz.

La práctica de utilizar enumeración proviene de un viejo estilo de programación de C.

Se empieza a utilizar una enumeración de los Estados Unidos, entonces necesitará el número de habitantes, la capital ..., y usted necesitará muchos interruptores grandes para obtener todas estas informaciones.

+0

Las entradas en C# no son objetos. Realmente no se derivan de una clase base o implementan una interfaz. Son solo constantes de tipo seguro. Pueden ser útiles cuando se usan como tales, pero son peligrosos si se los malinterpreta. – StriplingWarrior

+0

Sé que las enumeraciones no son un objeto. Esta es la razón por la que digo que son inútiles. De todos modos, dejé en claro mi respuesta. – onof

2

Usted podría utilizar TypeSafeEnum s clase

Aquí es una base

Public MustInherit Class AbstractTypeSafeEnum 
    Private Shared ReadOnly syncroot As New Object 
    Private Shared masterValue As Integer = 0 

    Protected ReadOnly _name As String 
    Protected ReadOnly _value As Integer 

    Protected Sub New(ByVal name As String) 
     Me._name = name 
     SyncLock syncroot 
      masterValue += 1 
      Me._value = masterValue 
     End SyncLock 
    End Sub 

    Public ReadOnly Property value() As Integer 
     Get 
      Return _value 
     End Get 
    End Property 

    Public Overrides Function ToString() As String 
     Return _name 
    End Function 

    Public Shared Operator =(ByVal ats1 As AbstractTypeSafeEnum, ByVal ats2 As AbstractTypeSafeEnum) As Boolean 
     Return (ats1._value = ats2._value) And Type.Equals(ats1.GetType, ats2.GetType) 
    End Operator 

    Public Shared Operator <>(ByVal ats1 As AbstractTypeSafeEnum, ByVal ats2 As AbstractTypeSafeEnum) As Boolean 
     Return Not (ats1 = ats2) 
    End Operator 

End Class 

Y aquí es una enumeración:

Public NotInheritable Class EnumProcType 
    Inherits AbstractTypeSafeEnum 

    Public Shared ReadOnly CREATE As New EnumProcType("Création") 
    Public Shared ReadOnly MODIF As New EnumProcType("Modification") 
    Public Shared ReadOnly DELETE As New EnumProcType("Suppression") 

    Private Sub New(ByVal name As String) 
     MyBase.New(name) 
    End Sub 

End Class 

Y lo que es más fácil añadir internacionalización.

Disculpa por el hecho de que está en VB y en francés.

¡Salud!

+0

Debido a que los valores enteros están mapeados en la base de datos, sería bueno hacer posible especificar valores int explícitos, para que no cambien si se reordenan. – StriplingWarrior

+0

Bueno, creo que no sería muy difícil cambiar el código para permitir eso. –

0

Como alternativa, puede usar las constantes

+0

¿Cómo sería el uso de constantes ventajoso aquí? – StriplingWarrior