¿Cómo comparar banderas en C#?
-
09-06-2019 - |
Pregunta
Tengo una enumeración de bandera a continuación.
[Flags]
public enum FlagTest
{
None = 0x0,
Flag1 = 0x1,
Flag2 = 0x2,
Flag3 = 0x4
}
No puedo hacer que la declaración if se evalúe como verdadera.
FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;
if (testItem == FlagTest.Flag1)
{
// Do something,
// however This is never true.
}
¿Cómo puedo hacer que esto sea cierto?
Solución
En .NET 4 hay un nuevo método Enum.HasFlag.Esto le permite escribir:
if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
// Do Stuff
}
que es mucho más legible, en mi opinión.
La fuente .NET indica que esto realiza la misma lógica que la respuesta aceptada:
public Boolean HasFlag(Enum flag) {
if (!this.GetType().IsEquivalentTo(flag.GetType())) {
throw new ArgumentException(
Environment.GetResourceString(
"Argument_EnumTypeDoesNotMatch",
flag.GetType(),
this.GetType()));
}
ulong uFlag = ToUInt64(flag.GetValue());
ulong uThis = ToUInt64(GetValue());
// test predicate
return ((uThis & uFlag) == uFlag);
}
Otros consejos
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do something
}
(testItem & FlagTest.Flag1)
es una operación AND bit a bit.
FlagTest.Flag1
es equivalente a 001
con la enumeración de OP.Ahora digamos testItem
tiene Flag1 y Flag2 (por lo que es bit a bit 101
):
001
&101
----
001 == FlagTest.Flag1
Para aquellos que tienen problemas para visualizar lo que está sucediendo con la solución aceptada (que es esta),
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do stuff.
}
testItem
(según la pregunta) se define como,
testItem
= flag1 | flag2
= 001 | 010
= 011
Entonces, en la declaración if, el lado izquierdo de la comparación es,
(testItem & flag1)
= (011 & 001)
= 001
Y la declaración if completa (que se evalúa como verdadera si flag1
está preparado testItem
),
(testItem & flag1) == flag1
= (001) == 001
= true
@phil-devaney
Tenga en cuenta que, excepto en los casos más simples, el Enum.HasFlag conlleva una gran penalización de rendimiento en comparación con escribir el código manualmente.Considere el siguiente código:
[Flags]
public enum TestFlags
{
One = 1,
Two = 2,
Three = 4,
Four = 8,
Five = 16,
Six = 32,
Seven = 64,
Eight = 128,
Nine = 256,
Ten = 512
}
class Program
{
static void Main(string[] args)
{
TestFlags f = TestFlags.Five; /* or any other enum */
bool result = false;
Stopwatch s = Stopwatch.StartNew();
for (int i = 0; i < 10000000; i++)
{
result |= f.HasFlag(TestFlags.Three);
}
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds); // *4793 ms*
s.Restart();
for (int i = 0; i < 10000000; i++)
{
result |= (f & TestFlags.Three) != 0;
}
s.Stop();
Console.WriteLine(s.ElapsedMilliseconds); // *27 ms*
Console.ReadLine();
}
}
Con más de 10 millones de iteraciones, el método de extensión HasFlags tarda la friolera de 4793 ms, en comparación con los 27 ms de la implementación bit a bit estándar.
Configuré un método de extensión para hacerlo: pregunta relacionada.
Básicamente:
public static bool IsSet( this Enum input, Enum matchTo )
{
return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
Entonces puedes hacer:
FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;
if( testItem.IsSet ( FlagTests.Flag1 ) )
//Flag1 is set
Por cierto, la convención que uso para las enumeraciones es singular para estándar y plural para banderas.De esa manera, sabrá por el nombre de la enumeración si puede contener múltiples valores.
Un consejo más...Nunca realice la verificación binaria estándar con la bandera cuyo valor es "0".Su verificación de esta bandera siempre será verdadera.
[Flags]
public enum LevelOfDetail
{
[EnumMember(Value = "FullInfo")]
FullInfo=0,
[EnumMember(Value = "BusinessData")]
BusinessData=1
}
Si verifica de forma binaria el parámetro de entrada con FullInfo, obtendrá:
detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;
bPRez siempre será verdadero como CUALQUIER COSA y 0 siempre == 0.
En su lugar, simplemente deberías comprobar que el valor de la entrada sea 0:
bool bPRez = (detailLevel == LevelOfDetail.FullInfo);
if((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
...
}
Para operaciones de bits, es necesario utilizar operadores bit a bit.
Esto debería funcionar:
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do something,
// however This is never true.
}
Editar: Se corrigió mi verificación if: volví a mis costumbres de C/C++ (gracias a Ryan Farley por señalarlo)
Respecto a la edición.No puedes hacerlo realidad.Le sugiero que incluya lo que desea en otra clase (o método de extensión) para acercarse a la sintaxis que necesita.
es decir.
public class FlagTestCompare
{
public static bool Compare(this FlagTest myFlag, FlagTest condition)
{
return ((myFlag & condition) == condition);
}
}
Prueba esto:
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// do something
}
Básicamente, su código pregunta si tener ambas banderas configuradas es lo mismo que tener una bandera configurada, lo cual obviamente es falso.El código anterior dejará solo el bit Flag1 establecido, si es que está configurado, luego comparará este resultado con Flag1.
incluso sin [Banderas], podrías usar algo como esto
if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}
o si tiene una enumeración de valor cero
if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}