27 votes

Conversion des ints en enums en C#

Il y a quelque chose que je n'arrive pas à comprendre en C#. Il est possible d'intégrer une valeur hors de l'intervalle int dans un enum et le compilateur ne bronche pas. Imaginez ceci enum :

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

Maintenant, si vous écrivez :

Colour eco;
eco = (Colour)17;

Le compilateur pense que c'est bien. 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'intérêt d'utiliser les enums, je pense, dans des scénarios comme celui-ci :

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

Dans un langage à fort typage comme C#, j'aimerais supposer que eco détiendra toujours une Colour valeur. Mais ce n'est pas le cas. Un programmeur pourrait appeler ma méthode avec une valeur de 17 affectée à eco (comme dans l'extrait de code précédent), donc le code de ma méthode ne doit pas supposer que eco détient un droit Colour valeur. Je dois le tester explicitement et traiter les valeurs exceptionnelles comme bon me semble. Pourquoi ?

À mon humble avis, il serait beaucoup plus agréable que le compilateur émette un message d'erreur (ou même d'avertissement) lors du moulage d'une valeur hors plage. int dans un enum si le int est connue au moment de la compilation. Si ce n'est pas le cas, le runtime doit lever une exception lors de l'instruction d'affectation.

Qu'en pensez-vous ? Y a-t-il une raison pour laquelle il en est ainsi ?

(Note. Il s'agit d'une question J'ai posté il y a longtemps sur mon blog mais n'a obtenu aucune réponse informative).

27voto

Henk Holterman Points 153608

Deviner le "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
string text = ne.ToString();  // text == "3"

Lorsque le [Flags] est placé devant l'enum, la dernière ligne se transforme en

string text = ne.ToString();  // text == "North, East"

13voto

johnny g Points 2472

Je ne sais pas trop 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 la suivante : comment puis-je tester la dernière clause de cas ? Il est tout à fait raisonnable de supposer que quelqu'un peut étendre TransmissionStatus et ne pas mettre à jour ses consommateurs, comme le pauvre petit MyClass ci-dessus. Pourtant, j'aimerais quand même vérifier son comportement dans ce scénario. L'une des solutions consiste à utiliser le casting, tel que

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

4voto

Bryan Rowe Points 3629

C'est l'une des nombreuses raisons pour lesquelles vous ne devriez jamais attribuer de valeurs entières à vos enums. S'ils ont des valeurs importantes qui doivent être utilisées dans d'autres parties du code, transformez l'enum en objet.

4voto

Mike Chamberlain Points 5325

Je comprends tout à fait le point de vue de César, et je me souviens qu'au départ, cela m'a également dérouté. À mon avis, les enums, dans leur implémentation actuelle, sont en effet un peu trop bas de gamme et fuient. Il me semble qu'il y aurait deux solutions à ce problème.

1) Ne permettre le stockage de valeurs arbitraires dans un enum que si sa définition comporte l'attribut FlagsAttribute. De cette façon, nous pouvons continuer à les utiliser pour un bitmask lorsque c'est approprié (et explicitement déclaré), mais lorsqu'ils sont utilisés simplement comme placeholders pour des constantes, la valeur est vérifiée au moment de l'exécution.

2) Introduire un type primitif séparé appelé, disons, bitmask, qui permettrait d'utiliser n'importe quelle valeur ulong. Encore une fois, nous limitons les enums standards aux seules valeurs déclarées. Ceci aurait l'avantage supplémentaire de permettre au compilateur d'assigner les valeurs des bits pour vous. Donc ceci :

[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 n'importe quel bitmask sont complètement prévisibles, n'est-ce pas ? En tant que programmeur, je suis plus qu'heureux d'avoir ce détail abstrait.

Mais c'est trop tard maintenant, je suppose que nous sommes coincés avec l'implémentation actuelle pour toujours. :/

4voto

Jaider Points 2366

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<Colour>().Contains(eco);
  if(isValid)
  {
     //It is really a valid Enum Colour. Here is safe!
  }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X