unity - enum multiple values c#
¿Cómo puedo evitar las combinaciones de valores de enumeración O a nivel de bit? (3)
Sé que puede usar FlagsAttribute
para indicar al compilador que use campos de bits para una enumeración.
¿Hay una manera de especificar que los valores de enumeración no se pueden combinar con OR a nivel de bits?
Ejemplo:
enum OnlyOneOption
{
Option1,
Option2,
...
}
En este ejemplo, no hay nada que OnlyOneOption.Option1 | OnlyOneOption.Option2
un desarrollador escriba OnlyOneOption.Option1 | OnlyOneOption.Option2
OnlyOneOption.Option1 | OnlyOneOption.Option2
. Me gustaría prohibirlo, en tiempo de compilación si es posible.
Verificación en tiempo de compilación
Recientemente, Eric Lippert (uno de los muchachos que trabajó en el compilador C # mientras estaba en Microsoft) blogged en su blogged acerca de sus 10 principales lamentos sobre C #, el número cuatro es que
En C #, una enumeración es solo una envoltura delgada del sistema de tipos sobre un tipo integral subyacente. Todas las operaciones en enumeraciones se especifican como operaciones reales en enteros, y los nombres de los valores enumerados son como constantes nombradas.
Así que, en principio, no puedes hacer que el compilador se ahogue
OnlyOneOption option = OnlyOneOption.Option1 | OnlyOneOption.Option2;
porque en términos de enteros, esa operación se ve perfectamente bien. Lo que puede hacer, como indica, no es proporcionar el FlagsAttribute
, que ya es una buena sugerencia para los desarrolladores.
Como tampoco puede sobrecargar a los operadores en las enum
, debe recurrir a la verificación en tiempo de ejecución.
Control en tiempo de ejecución
Lo que puede hacer es cuando necesite la enumeración, verifique la igualdad exacta y throw
una excepción cuando se usa una combinación de valores. La forma más rápida y limpia es usar un 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.");
}
Solución sin enumeración
Si no insiste en utilizar una enum
, podría recurrir al patrón estático clásico (Java-ish):
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();
}
y aquí la OnlyOneOption option = OnlyOneOption.OptionA | OnlyOneOption.OptionB;
OnlyOneOption option = OnlyOneOption.OptionA | OnlyOneOption.OptionB;
fallará con el error CS0019: " Operador ''|'' no puede aplicarse a operandos de tipo ''OnlyOneOption'' y ''OnlyOneOption'' ".
El inconveniente es que pierde la capacidad de escribir sentencias de switch
porque en realidad requieren una conversión constante en tiempo de compilación a int
(intenté proporcionar un static public implicit operator int
que devuelve un campo de readonly
, pero incluso eso no es suficiente - "Un valor constante se espera").
Añadir validación
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;
No, no puedes evitar eso. Si no se especifica el atributo [Flags]
, no se permitirá a un usuario lo siguiente:
enum Option
{
One = 1,
Two,
Three
}
var myOption = Option.One | Option.Two;
Además, algo perfectamente legal es lo siguiente:
var myOption = 0; //language quirk IMO, 0 being implicitly convertible to any enumeration type.
o
var myOption = (Option)33;
¿Es esto un problema? Pues no, en realidad no; Si su lógica solo considera las opciones One
, Two
o Three
, simplemente aplíquela:
public Foo Bar(Option myOption)
{
switch (myOption)
{
case Option.One: ...
case Option.Two: ...
case Option.Three: ...
default: //Not a valid option, act in consequence
}
}
Tenga en cuenta que si tiene un código (sospechoso) que decide en función del valor subyacente de la enumeración:
if (myOption < Option.Three) { //expecting myOption to be either One or Two...
Entonces definitivamente no estás usando la herramienta adecuada para el trabajo; myOption = 0;
o myOption = (Option)-999
sería un problema.