Lancer des enums en C #

Il y a quelque chose que je ne peux pas comprendre en C #. Vous pouvez convertir un int hors de gamme dans une enum et le compilateur ne bronche pas. Imaginez cet enum :

 enum Colour { Red = 1, Green = 2, Blue = 3 } 

Maintenant, si vous écrivez:

 Colour eco; eco = (Colour)17; 

Le compilateur pense que ça va. Et le runtime aussi. Euh

Pourquoi l’équipe C # a-t-elle décidé de rendre cela possible? Cette décision passe à côté de l’utilisation d’énums, je pense, dans des scénarios comme celui-ci:

 void DoSomethingWithColour(Colour eco) { //do something to eco. } 

Dans un langage très typé comme C #, je voudrais supposer que l’ eco aura toujours une valeur de Colour légale. Mais ce n’est pas le cas. Un programmeur peut appeler ma méthode avec une valeur de 17 atsortingbuée à eco (comme dans l’extrait de code précédent). Le code de ma méthode ne doit donc pas supposer eco contient une valeur de Colour légale. J’ai besoin de le tester explicitement et de gérer les valeurs exceptionnelles à ma guise. Pourquoi est-ce?

À mon humble avis, il serait beaucoup plus judicieux que le compilateur envoie un message d’erreur (ou même d’avertissement) lors du transtypage d’un int hors de scope dans une enum , si la valeur int est connue au moment de la compilation. Si ce n’est pas le cas, le moteur d’exécution devrait lancer une exception à l’instruction d’affectation.

Qu’est-ce que tu penses? Y a-t-il une raison pour cela?

(Remarque. Ceci est une question que j’ai postée il y a très longtemps sur mon blog mais qui n’a pas reçu de réponse informative.)

Deviner ‘pourquoi’ est toujours dangereux, mais considérez ceci:

 enum Direction { North =1, East = 2, South = 4, West = 8 } Direction ne = Direction.North | Direction.East; int value = (int) ne; // value == 3 ssortingng text = ne.ToSsortingng(); // text == "3" 

Lorsque l’atsortingbut [Flags] est placé devant l’énumération, la dernière ligne devient

 ssortingng text = ne.ToSsortingng(); // text == "North, East" 

Pas sûr de savoir pourquoi, mais j’ai récemment trouvé cette “fonctionnalité” incroyablement utile. J’ai écrit quelque chose comme ça l’autre jour

 // a simple enum public enum TransmissionStatus { Success = 0, Failure = 1, Error = 2, } // a consumer of enum public class MyClass { public void ProcessTransmissionStatus (TransmissionStatus status) { ... // an exhaustive switch statement, but only if // enum remains the same switch (status) { case TransmissionStatus.Success: ... break; case TransmissionStatus.Failure: ... break; case TransmissionStatus.Error: ... break; // should never be called, unless enum is // extended - which is entirely possible! // remember, code defensively! future proof! default: throw new NotSupportedException (); break; } ... } } 

la question est, comment puis-je tester cette dernière clause de cas? Il est tout à fait raisonnable de supposer qu’une personne peut étendre TransmissionStatus et ne pas mettre à jour ses consommateurs, comme le pauvre petit MyClass ci-dessus. Cependant, j’aimerais quand même vérifier son comportement dans ce scénario. L’une des méthodes consiste à utiliser un casting, tel que

 [Test] [ExpectedException (typeof (NotSupportedException))] public void Test_ProcessTransmissionStatus_ExtendedEnum () { MyClass myClass = new MyClass (); myClass.ProcessTransmissionStatus ((TransmissionStatus)(10)); } 

Je vois certainement le sharepoint César et je me souviens que cela m’a tout d’abord confondu. A mon avis, les enums, dans leur mise en oeuvre actuelle, sont en effet un peu trop bas et présentent des fuites. Il me semble qu’il y aurait deux solutions au problème.

1) Autorise uniquement le stockage de valeurs arbitraires dans une énumération si sa définition comporte l’atsortingbut FlagsAtsortingbute. De cette façon, nous pouvons continuer à les utiliser pour un masque de bits lorsque cela est approprié (et explicitement déclaré), mais si nous les utilisions simplement comme espaces réservés pour les constantes, nous obtiendrions la valeur vérifiée au moment de l’exécution.

2) Introduisez un type primitif distinct appelé say, bitmask, qui permettrait toute valeur ulong. Encore une fois, nous limitons les énumérations standard aux valeurs déclarées. Cela aurait l’avantage supplémentaire de permettre au compilateur d’atsortingbuer les valeurs de bits à votre place. Donc ça:

 [Flags] enum MyBitmask { FirstValue = 1, SecondValue = 2, ThirdValue = 4, FourthValue = 8 } 

serait équivalent à ceci:

 bitmask MyBitmask { FirstValue, SecondValue, ThirdValue, FourthValue } 

Après tout, les valeurs de tout masque de bits sont totalement prévisibles, non? En tant que programmeur, je suis plus qu’heureux que ce détail soit résumé.

Cependant, trop tard maintenant, je suppose que nous sums coincés avec la mise en œuvre actuelle pour toujours. : /

Vous n’avez pas besoin de faire face à des exceptions. La condition préalable de la méthode est que les appelants doivent utiliser l’énumération, et non pas lancer délibérément une entêtement inutile à l’énumération en question. Ce serait de la folie. N’est-ce pas le but des enums de ne pas utiliser les ints?

Tout développeur qui jetterait 17 dans l’énumération Color aurait besoin de 17 coups de pied en arrière en ce qui me concerne.

C’est l’une des nombreuses raisons pour lesquelles vous ne devriez jamais atsortingbuer de valeurs entières à vos enums. S’ils ont des valeurs importantes qui doivent être utilisées dans d’autres parties du code, convertissez l’énumération en object.

C’est inattendu … ce que nous voulons vraiment, c’est contrôler le casting … par exemple:

 Colour eco; if(Enum.TryParse("17", out eco)) //Parse successfully?? { var isValid = Enum.GetValues(typeof (Colour)).Cast().Contains(eco); if(isValid) { //It is really a valid Enum Colour. Here is safe! } } 

Lorsque vous définissez une énumération, vous donnez essentiellement un nom à une valeur (un sucre syntatique si vous voulez). Lorsque vous transformez 17 en couleur, vous enregistrez une valeur pour une couleur sans nom. Comme vous le savez probablement au final, il s’agit de toute façon d’un champ int.

Cela revient à déclarer un entier qui accepterait uniquement les valeurs de 1 à 100; La seule langue que j’ai jamais vue qui supportait ce niveau de vérification était CHILL.

 if (!Enum.IsDefined(typeof(Colour), 17)) { // Do something } 

Version courte:

Ne fais pas ça.

Tenter de transformer des enums en entiers en n’autorisant que des valeurs valides (peut-être une solution de secours par défaut) nécessite des méthodes d’assistance. À ce stade, vous n’avez pas d’enum – vous avez vraiment une classe.

Doublement, si l’intimité est importante – comme le disait Bryan Rowe.

Je pensais partager le code que j’ai utilisé pour valider Enums, car jusqu’à présent, rien ne semble fonctionner ici …

 public static class EnumHelper { public static bool IsValidValue(int value) { try { Parse(value.ToSsortingng()); } catch { return false; } return true; } public static T Parse(ssortingng value) { var values = GetValues(); int valueAsInt; var isInteger = Int32.TryParse(value, out valueAsInt); if(!values.Select(v => v.ToSsortingng()).Contains(value) && (!isInteger || !values.Select(v => Convert.ToInt32(v)).Contains(valueAsInt))) { throw new ArgumentException("Value '" + value + "' is not a valid value for " + typeof(T)); } return (T)Enum.Parse(typeof(T), value); } public static bool TryParse(ssortingng value, out T p) { try { p = Parse(value); return true; } catch (Exception) { p = default(T); return false; } } public static IEnumerable GetValues() { return Enum.GetValues(typeof (T)).Cast(); } } 

Vieille question, mais cela m’a confondu récemment, et Google m’a amené ici. J’ai trouvé un article avec une recherche plus poussée qui l’a finalement fait cliquer pour moi, et j’ai pensé revenir et partager.

L’essentiel est que Enum est une structure, ce qui signifie que c’est un type valeur ( source ). Mais il s’agit essentiellement d’un type dérivé du type sous-jacent (int, octet, long, etc.). Donc, si vous pouvez considérer un type Enum comme étant tout à fait la même chose que son type sous-jacent avec une fonctionnalité ajoutée / sucre syntaxique ajouté (comme le dit Otávio), alors vous serez conscient de ce “problème” et pourrez vous protéger contre ce dernier.

En parlant de ça, voici le cœur d’une méthode d’extension que j’ai écrite pour convertir / parsingr facilement les choses en un Enum:

 if (value != null) { TEnum result; if (Enum.TryParse(value.ToSsortingng(), true, out result)) { // since an out-of-range int can be cast to TEnum, double-check that result is valid if (Enum.IsDefined(typeof(TEnum), result.ToSsortingng())) { return result; } } } // deal with null and defaults... 

La value variable il y a de type object, car cette extension a des “surcharges” acceptant int, int ?, ssortingng, Enum et Enum ?. Ils sont tous encadrés et envoyés à la méthode privée qui effectue l’parsing. En utilisant TryParse et IsDefined dans cet ordre , je peux parsingr tous les types susmentionnés, y compris les chaînes de casse mixte, et il est plutôt robuste.

L’utilisation est comme ça (suppose NUnit):

 [Test] public void MultipleInputTypeSample() { int source; SampleEnum result; // valid int value source = 0; result = source.ParseToEnum(); Assert.That(result, Is.EqualTo(SampleEnum.Value1)); // out of range int value source = 15; Assert.Throws(() => source.ParseToEnum()); // out of range int with default provided source = 30; result = source.ParseToEnum(SampleEnum.Value2); Assert.That(result, Is.EqualTo(SampleEnum.Value2)); } private enum SampleEnum { Value1, Value2 } 

J’espère que ça aide quelqu’un.

Clause de non-responsabilité: nous n’utilisons pas d’énumérations flags / bitmask … cela n’a pas été testé avec ce scénario d’utilisation et ne fonctionnerait probablement pas.

Cela provoquerait une erreur en utilisant le Enum.Parse ();

 Enum parsedColour = (Colour)Enum.Parse(typeof(Colour), "17"); 

Cela vaut peut-être la peine d’utiliser une erreur d’exécution, si vous le souhaitez.