Vraag Hoe kan ik bitwise OF-combinaties van opsommingswaarden voorkomen?


Ik weet dat je kunt gebruiken FlagsAttribute om de compiler opdracht te geven bitvelden te gebruiken voor een opsomming.

Is er een manier om op te geven dat enum-waarden niet kunnen worden gecombineerd met bitsgewijze OR?

Voorbeeld:

enum OnlyOneOption
{
   Option1,
   Option2,
   ...
}

In dit voorbeeld is er niets dat een ontwikkelaar ervan weerhoudt om te schrijven OnlyOneOption.Option1 | OnlyOneOption.Option2. Ik zou het willen verbieden, indien mogelijk tijdens het compileren.


22
2017-07-22 09:07


oorsprong


antwoorden:


Compilatiecontrole

Onlangs Eric Lippert (een van de jongens die aan de C # -compiler werkte toen hij bij Microsoft was) geblogd over zijn top 10 spijt over C #, nummer vier is dat

In C # is een enum slechts een dunne wrapper van het type systeem over een onderliggend integraal type. Alle bewerkingen in de enums worden gespecificeerd als feitelijk operaties op gehele getallen, en de namen van enum-waarden zijn gelijk aan benoemde constanten.

Dus in principe kun je de compiler niet laten stikken

OnlyOneOption option = OnlyOneOption.Option1 | OnlyOneOption.Option2;

omdat in termen van gehele getallen, die operatie er prima uitziet. Wat je kunt doen, zoals je aangeeft, is niet lever de FlagsAttribute - wat al een goede hint is voor ontwikkelaars.

Sinds je kunt operators niet overbelasten op enums, u moet ook gebruik maken van runtime checking.

Run-time controle

Wat je kunt doen, is wanneer je de enum nodig hebt, controleer op exacte gelijkheid en throw een uitzondering wanneer een combinatie van waarden wordt gebruikt. De snelste en schoonste manier is om a te gebruiken switch:

// Use the bit pattern to guarantee that e.g. OptionX | OptionY
// never accidentally ends up as another valid option.
enum OnlyOneOption { Option1 = 0x001, Option2 = 0x002, Option3 = 0x004, ... };

switch(option) {
  case OnlyOneOption.Option1: 
    // Only Option1 selected - handle it.
    break;

  case OnlyOneOption.Option2: 
    // Only Option2 selected - handle it.
    break;

  default:
    throw new InvalidOperationException("You cannot combine OnlyOneOption values.");
}

Niet-enum oplossing

Als u niet aandringt op het gebruik van een enum, je zou kunnen terugvallen op het klassieke (Java-achtige) statische patroon:

class OnlyOneOption
{
    // Constructor is private so users cannot create their own instances.
    private OnlyOneOption() {} 

    public static OnlyOneOption OptionA = new OnlyOneOption();
    public static OnlyOneOption OptionB = new OnlyOneOption();
    public static OnlyOneOption OptionC = new OnlyOneOption();
}

en hier OnlyOneOption option = OnlyOneOption.OptionA | OnlyOneOption.OptionB; zal mislukken met fout CS0019: "Operator '|' kan niet worden toegepast op operanden van het type 'OnlyOneOption' en 'OnlyOneOption'".

Het nadeel is dat je het vermogen om te schrijven kwijtraakt switch omdat ze eigenlijk een constante conversie van de compileertijd vereisen int (Ik heb geprobeerd om een static public implicit operator int dat levert a op readonly veld maar zelfs dat is niet genoeg - "Er wordt een constante waarde verwacht").


26
2017-07-22 09:25



Nee, dat kan je niet voorkomen. Geen specificeren [Flags] attribuut zal een gebruiker niet van het volgende uitsluiten:

enum Option
{
    One = 1,
    Two,
    Three
}

var myOption = Option.One | Option.Two;

Verder is iets volkomen legaal het volgende:

var myOption = 0; //language quirk IMO, 0 being implicitly convertible to any enumeration type.

of

var myOption = (Option)33;

Is dit een probleem? Nou nee, niet echt; als uw logica alleen opties overweegt One, Two of Three dan gewoon afdwingen:

public Foo Bar(Option myOption)
{
     switch (myOption)
     {
         case Option.One: ...
         case Option.Two: ...
         case Option.Three: ...
         default: //Not a valid option, act in consequence
     }
}

Merk op dat als u (verdachte) code heeft die beslist op basis van de onderliggende waarde van de opsomming:

if (myOption < Option.Three) { //expecting myOption to be either One or Two...

Dan gebruikt u zeker niet de juiste tool voor de klus; myOption = 0; of myOption = (Option)-999 zou een probleem zijn.


5
2017-07-22 09:38



Validatie toevoegen

enum MyEnum
{
  Value1 = 1,
  Value2 = 2
}

  MyEnum e = MyEnum.Value1 | MyEnum.Value2;
  var isValid = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>().Count(x => e.HasFlag(x)) == 1;

2
2017-07-22 09:32