Como Comparar Sinalizadores em C#?
-
09-06-2019 - |
Pergunta
Eu tenho uma bandeira de enum abaixo.
[Flags]
public enum FlagTest
{
None = 0x0,
Flag1 = 0x1,
Flag2 = 0x2,
Flag3 = 0x4
}
Eu não posso fazer a instrução se é avaliada como verdadeira.
FlagTest testItem = FlagTest.Flag1 | FlagTest.Flag2;
if (testItem == FlagTest.Flag1)
{
// Do something,
// however This is never true.
}
Como posso tornar isso verdade?
Solução
Em .NET 4 há um novo método Enum.HasFlag.Isto permite-lhe escrever:
if ( testItem.HasFlag( FlagTest.Flag1 ) )
{
// Do Stuff
}
o que é muito mais legível, IMO.
A .Fonte LÍQUIDA indica que executa a mesma lógica que o aceite resposta:
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);
}
Outras dicas
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do something
}
(testItem & FlagTest.Flag1)
é uma operação AND bit a bit.
FlagTest.Flag1
é equivalente a 001
com o OP é o enum.Agora vamos dizer que testItem
tem Sinalizador1 e Flag2 (então ele bit a bit 101
):
001
&101
----
001 == FlagTest.Flag1
Para aqueles que têm dificuldade de se visualizar o que está acontecendo com o aceite da solução (que é este),
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do stuff.
}
testItem
(como por pergunta) é definido como,
testItem
= flag1 | flag2
= 001 | 010
= 011
Em seguida, a instrução se, o lado esquerdo da comparação é,
(testItem & flag1)
= (011 & 001)
= 001
E o completo se a instrução (que é avaliada como true se flag1
é definido em testItem
),
(testItem & flag1) == flag1
= (001) == 001
= true
@phil-devaney
Tenha em atenção que excepto, no mais simples dos casos, os Enum.HasFlag carrega uma pesada penalidade de desempenho em comparação ao escrever o código manualmente.Considere o seguinte 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();
}
}
Mais de 10 milhões de iterações, o HasFlags método de extensão leva uma enorme 4793 ms, em comparação com os 27 ms para o padrão de bit a bit implementação.
Para configurar um método de extensão para fazê-lo: perguntas relacionadas.
Basicamente:
public static bool IsSet( this Enum input, Enum matchTo )
{
return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
Em seguida, você pode fazer:
FlagTests testItem = FlagTests.Flag1 | FlagTests.Flag2;
if( testItem.IsSet ( FlagTests.Flag1 ) )
//Flag1 is set
Aliás, a convenção que eu uso para enums é singular por padrão, no plural para bandeiras.De que forma você sabe do enum nome se ele pode armazenar vários valores.
Mais um conselho...Nunca faça o padrão binário de seleção com a bandeira cujo valor é "0".Seu check esta bandeira será sempre verdadeiro.
[Flags]
public enum LevelOfDetail
{
[EnumMember(Value = "FullInfo")]
FullInfo=0,
[EnumMember(Value = "BusinessData")]
BusinessData=1
}
Se você binário de seleção de parâmetro de entrada contra FullInfo - você ganha:
detailLevel = LevelOfDetail.BusinessData;
bool bPRez = (detailLevel & LevelOfDetail.FullInfo) == LevelOfDetail.FullInfo;
bPRez sempre será verdadeiro como QUALQUER coisa & 0 sempre == 0.
Em vez disso, você deve simplesmente verificar que o valor da entrada é de 0:
bool bPRez = (detailLevel == LevelOfDetail.FullInfo);
if((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
...
}
Para operações bit, você precisa usar os operadores bit a bit.
Isso deve fazer o truque:
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// Do something,
// however This is never true.
}
Editar: Fixo os meus se o check - eu escorreguei de volta em minha C/C++ formas (graças a Ryan Farley por apontando para fora)
Sobre o editar.Você não pode torná-la verdadeira.Eu sugiro que você enrole o que você quer em outra classe (ou método de extensão) para aproximar a sintaxe que você precisa.
i.e.
public class FlagTestCompare
{
public static bool Compare(this FlagTest myFlag, FlagTest condition)
{
return ((myFlag & condition) == condition);
}
}
Tente isso:
if ((testItem & FlagTest.Flag1) == FlagTest.Flag1)
{
// do something
}
Basicamente, o seu código está perguntando se tendo tanto o conjunto de sinalizadores é o mesmo que ter um conjunto de sinalizador, o que é obviamente falso.O código acima vai deixar apenas o Flag1 conjunto de bits se estiver definido, então compara este resultado com Flag1.
mesmo sem [Flags], você pode usar algo como isto
if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=0){
//..
}
ou se você tiver um valor igual a Zero enum
if((testItem & (FlagTest.Flag1 | FlagTest.Flag2 ))!=FlagTest.None){
//..
}