Enum et performance

Mon application a beaucoup de valeurs de recherche différentes, ces valeurs ne changent jamais, par exemple les États américains. Plutôt que de les mettre dans des tables de base de données, j'aimerais utiliser des énumérations.

Mais, je me rends compte que le faire de cette façon implique d'avoir quelques énumérations et beaucoup de casting de "int" et "string" vers et depuis mes énumérations.

Alternative, je vois quelqu'un mentionné en utilisant un dictionnaire comme une table de recherche, mais l'implémentation d'enum semble être plus propre.

Donc, je voudrais demander si garder et passer autour de beaucoup d'énumérations et les lancer est un problème de performance ou devrais-je utiliser l'approche des tables de recherche, qui fonctionne mieux?

Edit: le casting est nécessaire comme ID pour être stocké dans d'autres tables de base de données.

24
demandé sur abatishchev 2010-07-15 18:46:36

6 réponses

Lancer de int vers une énumération est extrêmement bon marché... ce sera plus rapide qu'une recherche de dictionnaire. Fondamentalement, c'est un no-op, il suffit de copier les bits dans un emplacement avec un type notionnel différent.

L'analyse d'une chaîne dans une valeur enum sera un peu plus lente.

Je doute que cela va être un goulot d'étranglement pour vous mais vous le faites si, pour être honnête... sans en savoir plus sur ce que vous faites, il est un peu difficile de recommander au-delà de la normale " écrire le plus simple, mode code lisible et maintenable qui fonctionnera, puis vérifiez qu'il fonctionne assez bien."

31
répondu Jon Skeet 2010-07-15 14:50:10

Vous n'allez pas remarquer une grande différence de performance entre les deux, mais je recommanderais toujours d'utiliser un dictionnaire car cela vous donnera un peu plus de flexibilité à l'avenir.

D'une part, une énumération en C# ne peut pas automatiquement avoir une classe associée comme en Java, donc si vous voulez associer des informations supplémentaires à un État (Nom Complet, capitale, abréviation postale, etc.), la création d'une classe UnitedState facilitera l'empaquetage de toutes ces informations dans une seule collection.

De plus, même si vous pensez que cette valeur ne changera jamais, elle n'est pas parfaitement immuable. Vous pourriez éventuellement avoir une nouvelle exigence d'inclure des territoires, par exemple. Ou peut-être que vous devrez permettre aux utilisateurs canadiens de voir les noms des Provinces canadiennes à la place. Si vous traitez cette collection comme toute autre collection de données (en utilisant un référentiel pour en extraire des valeurs), vous aurez plus tard la possibilité de modifier l'implémentation de votre référentiel pour extraire valeurs provenant d'une source différente (base de données, Service Web, Session, etc.). Les Enums sont beaucoup moins polyvalents.

Modifier

En ce qui concerne l'argument de performance: gardez à l'esprit que vous n'êtes pas juste lancer une énumération à un int: vous exécutez également ToString() sur cette énumération, ce qui ajoute un temps de traitement considérable. Considérons le test suivant:

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);

Résultats:

Enum: 26.4875
Dictionary: 0.7684

Donc, si la performance est vraiment votre principale préoccupation, un dictionnaire est certainement la voie à suivre. Cependant, nous parlons de temps si rapides ici qu'il y a une demi-douzaine d'autres préoccupations que j'aborderais avant même que je me soucie de la question de la vitesse.

Les énumérations en C# n'ont pas été conçues pour fournir des mappages entre les valeurs et les chaînes. Ils ont été conçus pour fournir des valeurs constantes fortement typées que vous pouvez transmettre dans le code. Les deux principaux avantages de ceci sont:

  1. Vous avez un indice supplémentaire vérifié par le compilateur pour vous aider à éviter de passer des arguments dans le mauvais ordre, etc.
  2. plutôt que de mettre des valeurs numériques "magiques" (par exemple "42") dans votre code, vous pouvez dire "States".Oklahoma", qui rend votre code plus lisible.

Contrairement à Java, C# ne vérifie pas automatiquement les valeurs de conversion pour s'assurer qu'elles sont valides (myState = (States)321), de sorte que vous n'obtenez aucune vérification des données d'exécution sur les entrées sans les faire manuellement. Si vous n'avez pas de code qui fait référence aux États explicitement ("States.Oklahoma"), alors vous n'obtenez aucune valeur de #2 ci-dessus. Cela nous laisse avec #1 comme la seule vraie raison d'utiliser des énumérations. Si c'est une raison suffisante pour vous, alors je suggère d'utiliser enums au lieu de ints comme valeurs clés. Ensuite, lorsque vous avez besoin d'une chaîne ou d'une autre valeur liée à l'état, effectuez une recherche de dictionnaire.

Voici comment je le ferais:

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);
    }
}

De Cette façon:

  1. Vous pouvez passer des valeurs fortement typées qui représentent un État,
  2. votre recherche obtient un comportement rapide si elle est invalide entrée,
  3. Vous pouvez facilement changer l'emplacement des données d'état réelles dans le futur,
  4. Vous pouvez facilement ajouter des données liées à l'État à la classe D'État à l'avenir,
  5. Vous pouvez facilement ajouter de nouveaux États, territoires, districts, provinces, ou quoi que ce soit d'autre à l'avenir.
  6. obtenir un nom à partir d'un int est toujours environ 15 fois plus rapide que lorsque vous utilisez Enum.ToString().

[grognement]

15
répondu StriplingWarrior 2010-07-15 20:40:39

Vous pouvez utiliser TypeSafeEnum s

Voici une classe de 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

Et voici une énumération:

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

Et il devient plus facile d'ajouter L'internationalisation.

Désolé pour le fait que c'est en VB et en français.

Cheers !

2
répondu Alex Rouillard 2010-07-15 15:15:01

Les énumérations surperformeront grandement presque n'importe quoi, en particulier les dictionary. Enums n'utilisent qu'un seul octet. Mais pourquoi ferais-tu un casting? On dirait que vous devriez utiliser les énumérations partout.

0
répondu MrFox 2010-07-15 14:53:17

Vous pouvez également utiliser des constantes

0
répondu Guru 2010-07-15 17:37:25

Évitez l'énumération comme vous le pouvez: les énumérations doivent être remplacées par des singletons dérivant de la classe de base ou implémentant une interface.

La pratique de l'utilisation d'enum provient d'une programmation de style ancien de C.

Vous commencez à utiliser une énumération pour les États américains, alors vous aurez besoin du nombre d'habitants, le Capitole..., et vous aurez besoin de beaucoup de gros commutateurs pour obtenir toutes ces infos.

-2
répondu onof 2010-07-16 06:16:48