Bayrakları olan bir Enum değerlerini nasıl yineleyebilirim?


131

Bayrak numaralandıran bir değişkenim varsa, bu belirli değişkendeki bit değerleri üzerinde bir şekilde yineleme yapabilir miyim? Yoksa tüm numaralandırmayı yinelemek ve hangilerinin ayarlandığını kontrol etmek için Enum.GetValues ​​kullanmam gerekir mi?


API'niz üzerinde kontrole sahipseniz, bit bayrakları kullanmaktan kaçının. Nadiren faydalı bir optimizasyondurlar. Birkaç genel 'bool' alanına sahip bir yapı kullanmak anlamsal olarak eşdeğerdir ancak kodunuz önemli ölçüde daha basittir. Ve daha sonra ihtiyacınız olursa, alanları dahili olarak bit alanlarını işleyen özelliklere değiştirebilir ve optimizasyonu özetleyebilirsiniz.
Jay Bazuzi

2
Ne söylediğinizi anlıyorum ve çoğu durumda mantıklı geliyor, ancak bu durumda, If önerisi ile aynı sorunu yaşıyordum ... Bunun yerine, bir düzine farklı bool için If ifadeleri yazmam gerekir bir dizi üzerinde basit bir foreach döngüsü kullanma. (Ve bu genel bir DLL'nin parçası olduğu için, sadece bir dizi

10
"kodunuz çok daha basittir" - sadece tek tek bitleri test etmekten başka bir şey yapıyorsanız, tamamen tersi doğrudur ... alanlar ve özellikler birinci sınıf varlıklar olmadığından döngüler ve küme işlemleri neredeyse imkansız hale gelir.
Jim Balter

2
@nawfal "biraz" Orada ne yaptığını görüyorum
stannius

Yanıtlar:


180
static IEnumerable<Enum> GetFlags(Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value))
            yield return value;
}

7
HasFlagNET 4'ten itibaren mevcut olduğunu unutmayın .
Andreas Grech

3
Bu harika! Ancak daha da basit ve kullanımı kolay hale getirebilirsiniz. Bunu bir uzatma yöntemi olarak yapıştırmanız yeterli: Enum.GetValues(input.GetType()).Cast<Enum>().Where(input.HasFlag); Sonra sadece: myEnum.GetFLags():)
joshcomley

3
Harika tek satırlık, josh, ancak yine de, yukarıda Jeff'in cevabında olduğu gibi, yalnızca tek bayraklı değerler (Bar, Baz) yerine çoklu bayrak değerleri (Boo) toplama problemi yaşıyor.

10
Güzel - Hiçbirine dikkat et - örneğin Öğeler. Jeff'in cevabından hiçbiri her zaman dahil edilmeyecek
Ilan

1
Yöntem imzası şöyle olmalıdırstatic IEnumerable<Enum> GetFlags(this Enum input)
Erwin Rooijakkers

48

İşte soruna bir Linq çözümü.

public static IEnumerable<Enum> GetFlags(this Enum e)
{
      return Enum.GetValues(e.GetType()).Cast<Enum>().Where(e.HasFlag);
}

7
Bu neden en üstte değil? :) .Where(v => !Equals((int)(object)v, 0) && e.HasFlag(v));Temsil edecek sıfır değeriniz varsa kullanınNone
georgiosd

Çok temiz. Bence en iyi çözüm.
Ryan Fiorini

@georgiosd Sanırım performans o kadar da iyi değil. (Ancak çoğu görev için yeterince iyi olmalı)
AntiHeadshot

41

Bildiğim kadarıyla her bileşeni elde etmek için herhangi bir yerleşik yöntem yok. Ama bunları elde etmenin bir yolu:

[Flags]
enum Items
{
    None = 0x0,
    Foo  = 0x1,
    Bar  = 0x2,
    Baz  = 0x4,
    Boo  = 0x6,
}

var value = Items.Foo | Items.Bar;
var values = value.ToString()
                  .Split(new[] { ", " }, StringSplitOptions.None)
                  .Select(v => (Items)Enum.Parse(typeof(Items), v));

// This method will always end up with the most applicable values
value = Items.Bar | Items.Baz;
values = value.ToString()
              .Split(new[] { ", " }, StringSplitOptions.None)
              .Select(v => (Items)Enum.Parse(typeof(Items), v)); // Boo

Bunun Enumyerine bayrakları döndürmek için dizeyi oluşturmak için dahili olarak neyin yapılacağını uyarladım . Reflektördeki koda bakabilirsiniz ve aşağı yukarı eşdeğer olmalıdır. Birden çok bit içeren değerlerin olduğu genel kullanım durumları için iyi sonuç verir.

static class EnumExtensions
{
    public static IEnumerable<Enum> GetFlags(this Enum value)
    {
        return GetFlags(value, Enum.GetValues(value.GetType()).Cast<Enum>().ToArray());
    }

    public static IEnumerable<Enum> GetIndividualFlags(this Enum value)
    {
        return GetFlags(value, GetFlagValues(value.GetType()).ToArray());
    }

    private static IEnumerable<Enum> GetFlags(Enum value, Enum[] values)
    {
        ulong bits = Convert.ToUInt64(value);
        List<Enum> results = new List<Enum>();
        for (int i = values.Length - 1; i >= 0; i--)
        {
            ulong mask = Convert.ToUInt64(values[i]);
            if (i == 0 && mask == 0L)
                break;
            if ((bits & mask) == mask)
            {
                results.Add(values[i]);
                bits -= mask;
            }
        }
        if (bits != 0L)
            return Enumerable.Empty<Enum>();
        if (Convert.ToUInt64(value) != 0L)
            return results.Reverse<Enum>();
        if (bits == Convert.ToUInt64(value) && values.Length > 0 && Convert.ToUInt64(values[0]) == 0L)
            return values.Take(1);
        return Enumerable.Empty<Enum>();
    }

    private static IEnumerable<Enum> GetFlagValues(Type enumType)
    {
        ulong flag = 0x1;
        foreach (var value in Enum.GetValues(enumType).Cast<Enum>())
        {
            ulong bits = Convert.ToUInt64(value);
            if (bits == 0L)
                //yield return value;
                continue; // skip the zero value
            while (flag < bits) flag <<= 1;
            if (flag == bits)
                yield return value;
        }
    }
}

Uzantı yöntemi GetIndividualFlags(), bir tür için tüm bağımsız bayrakları alır. Böylece birden çok bit içeren değerler dışarıda bırakılır.

var value = Items.Bar | Items.Baz;
value.GetFlags();           // Boo
value.GetIndividualFlags(); // Bar, Baz

Bir dizi bölme yapmayı düşünmüştüm, ancak bu muhtemelen tüm numaralamanın bit değerlerini yinelemekten çok daha fazla ek yük.

Maalesef bunu yaparak, fazlalık değerleri test etmeniz gerekir (eğer istemezseniz). Benim ikinci örneğe bakın, bu doğuracak Bar, Bazve Boobunun yerine sadece bir Boo.
Jeff Mercado

Yaptığım şey için bu kısım gereksiz (ve aslında, gerçekten kötü bir fikir :)) olsa da, Boo'yu bundan kurtarabilmeniz ilginç.

Bu ilginç görünüyor, ancak yanlış anlamıyorsam, yukarıdaki numaralandırma için Boo döndürür, burada yalnızca diğer değerlerin birleşimi olmayan sürümleri (yani ikinin güçleri olanları) numaralandırmak isterdim. . Bu kolayca yapılabilir mi? Deniyorum ve FP matematiğine başvurmadan bunu tanımlamanın kolay bir yolunu düşünemiyorum.

@Robin: Haklısın, orijinal geri dönecekti Boo(kullanılarak döndürülen değer ToString()). Yalnızca tek tek bayraklara izin verecek şekilde ince ayar yaptım. Yani benim örneğimde, Barve Bazyerine alabilirsiniz Boo.
Jeff Mercado

26

Birkaç yıl sonra, biraz daha deneyimle buna geri dönersek, yalnızca tek bitlik değerler için nihai cevabım, en düşük bitten en yüksek bit'e geçerken, Jeff Mercado'nun iç rutininin hafif bir çeşidi:

public static IEnumerable<Enum> GetUniqueFlags(this Enum flags)
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<Enum>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value))
        {
            yield return value;
        }
    }
}

İşe yarıyor gibi görünüyor ve birkaç yıl önceki itirazlarıma rağmen, burada HasFlag kullanıyorum, çünkü bit tabanlı karşılaştırmalar kullanmaktan çok daha okunaklı ve yapacağım her şey için hız farkı önemsiz. (O zamandan beri HasFlag'lerin hızını artırmış olmaları tamamen mümkündür, tek bildiğim ... Test etmedim.)


Sadece bir not bayrak olmalıdır bir int başlatılan bir tür bit olarak Ulong olarak başlatılması gereken 1ul
forcewill

Teşekkürler, bunu düzelteceğim! (Sadece gittim ve gerçek kodumu kontrol ettim ve özellikle bir ulong olduğunu bildirerek onu tam tersi şekilde düzelttim.)

2
"Yok" u temsil etmesi gereken sıfır değerine sahip bir bayrağınız varsa, diğer yanıtlar GetFlag () yöntemlerinin YourEnum.None yöntemlerini bayraklardan biri olarak döndüreceği gerçeğinden de muzdarip olmadığını bulduğum tek çözüm budur. aslında enum'da olmasa bile, yöntemi çalıştırıyorsunuz! Tuhaf yinelenen günlük girişleri alıyordum çünkü yöntemler, yalnızca bir sıfır olmayan enum bayrağı ayarlandığında beklediğimden daha fazla kez çalışıyordu. Bu harika çözümü güncellemek ve eklemek için zaman ayırdığınız için teşekkür ederiz!
BrianH

yield return bits;?
Jaider

1
Ulong.MaxValue atadığım bir "All" enum değişkeni istedim, bu yüzden tüm bitler '1' olarak ayarlandı. Ancak kodunuz sonsuz bir döngüye çar, çünkü flag <bitler asla doğru olarak değerlendirilmez (bayrak döngüleri negatife döner ve sonra 0'da takılı kalır).
Haighstrom

15

@ Greg'in yönteminden çıkmak, ancak C # 7.3'ten yeni bir özellik eklemek, Enumkısıtlama:

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
    where T : Enum    // New constraint for C# 7.3
{
    foreach (Enum value in Enum.GetValues(flags.GetType()))
        if (flags.HasFlag(value))
            yield return (T)value;
}

Yeni kısıtlama yoluyla döküm zorunda kalmadan, bu uzantı yöntem sağlar (int)(object)e, ve kullanabilir HasFlagdoğrudan yöntem ve bir döküm Tden value.

C # 7.3 ayrıca delagates için kısıtlamalar ekledi ve unmanaged.


4
Muhtemelen flagsparametrenin de genel türde olmasını istediniz T, aksi takdirde her çağırdığınızda numaralandırma türünü açıkça belirtmeniz gerekir.
Ray

11

@ RobinHood70 tarafından sağlanan yanıt için +1. Metodun genel bir versiyonunun benim için uygun olduğunu buldum.

public static IEnumerable<T> GetUniqueFlags<T>(this Enum flags)
{
    if (!typeof(T).IsEnum)
        throw new ArgumentException("The generic type parameter must be an Enum.");

    if (flags.GetType() != typeof(T))
        throw new ArgumentException("The generic type parameter does not match the target type.");

    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}

@AustinWBryan için C # 7.3'ü çözüm alanına taşıdığı için EDIT And +1.

public static IEnumerable<T> GetUniqueFlags<T>(this T flags) where T : Enum
{
    ulong flag = 1;
    foreach (var value in Enum.GetValues(flags.GetType()).Cast<T>())
    {
        ulong bits = Convert.ToUInt64(value);
        while (flag < bits)
        {
            flag <<= 1;
        }

        if (flag == bits && flags.HasFlag(value as Enum))
        {
            yield return value;
        }
    }
}

3

Tüm değerleri yinelemenize gerek yok. sadece belirli bayraklarınızı şu şekilde kontrol edin:

if((myVar & FlagsEnum.Flag1) == FlagsEnum.Flag1) 
{
   //do something...
}

veya ( pstrjds'nin yorumlarda söylediği gibi ) aşağıdaki gibi kullanımını kontrol edebilirsiniz:

if(myVar.HasFlag(FlagsEnum.Flag1))
{
   //do something...
}

5
.Net 4.0 kullanıyorsanız, aynı şeyi yapmak için kullanabileceğiniz bir HasFlag genişletme yöntemi vardır: myVar.HasFlag (FlagsEnum.Flag1)
pstrjds

1
Bir programcı bit bazında VE işlemi anlayamazsa, onu paketlemeli ve yeni bir kariyer bulmalıdır.
Ed S.

2
@Ed: doğru, ancak HasFlag kodu tekrar okurken daha iyi ... (belki birkaç ay veya yıl sonra)
Dr TJ

4
@Ed Swangren: Bu gerçekten kodu daha okunaklı ve daha az ayrıntılı yapmakla ilgili, bitsel işlemleri kullanmak "zor" olduğu için zorunlu değil.
Jeff Mercado

2
HasFlag son derece yavaştır. HasFlag ve bit maskeleme kullanarak büyük bir döngü deneyin ve büyük bir fark göreceksiniz.

3

Yaptığım şey yaklaşımımı değiştirmekti, yöntemin input parametresini tür olarak yazmak yerine, enumonu enumtype ( MyEnum[] myEnums) türünün bir dizisi olarak yazdım, bu şekilde diziyi döngü içinde bir switch ifadesiyle yineledim.


2

Başlangıç ​​olmasına rağmen yukarıdaki cevaplardan memnun kalmadı.

Burada bazı farklı kaynakları bir araya getirdikten sonra:
Bu konudaki önceki poster SO QnA Code Project Enum Flags Gönderiyi
Kontrol Et
Great Enum <T> Utility

Bunu ben yarattım, ne düşündüğünüzü bana bildirin.
Parametreler:: bir bayrak değeri olarak
bool checkZeroizin vermesini söyler 0. Varsayılan olarak input = 0boş döner.
bool checkFlags: öznitelikle Enumdekore edilip edilmediğini kontrol etmesini söyler [Flags].
PS. Şu anda checkCombinators = false, bitlerin birleşimi olan herhangi bir enum değerini görmezden gelmeye zorlayacak alg'ı bulmak için zamanım yok.

    public static IEnumerable<TEnum> GetFlags<TEnum>(this TEnum input, bool checkZero = false, bool checkFlags = true, bool checkCombinators = true)
    {
        Type enumType = typeof(TEnum);
        if (!enumType.IsEnum)
            yield break;

        ulong setBits = Convert.ToUInt64(input);
        // if no flags are set, return empty
        if (!checkZero && (0 == setBits))
            yield break;

        // if it's not a flag enum, return empty
        if (checkFlags && !input.GetType().IsDefined(typeof(FlagsAttribute), false))
            yield break;

        if (checkCombinators)
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum<TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }
        else
        {
            // check each enum value mask if it is in input bits
            foreach (TEnum value in Enum <TEnum>.GetValues())
            {
                ulong valMask = Convert.ToUInt64(value);

                if ((setBits & valMask) == valMask)
                    yield return value;
            }
        }

    }

Bu, burada kullanmak yield returnüzere güncellediğim Yardımcı Sınıf Enum <T> 'nu kullanır GetValues:

public static class Enum<TEnum>
{
    public static TEnum Parse(string value)
    {
        return (TEnum)Enum.Parse(typeof(TEnum), value);
    }

    public static IEnumerable<TEnum> GetValues()   
    {
        foreach (object value in Enum.GetValues(typeof(TEnum)))
            yield return ((TEnum)value);
    }
}  

Son olarak, işte bunun kullanımına bir örnek:

    private List<CountType> GetCountTypes(CountType countTypes)
    {
        List<CountType> cts = new List<CountType>();

        foreach (var ct in countTypes.GetFlags())
            cts.Add(ct);

        return cts;
    }

Üzgünüm, birkaç gündür bu projeye bakacak vaktim olmadı. Kodunuza daha iyi baktıktan sonra size geri döneceğim.

4
Bu kodda bir hata olduğuna dair bir uyarı. İf (checkCombinators) ifadesinin her iki dalındaki kod aynıdır. Ayrıca, belki bir hata değil, ancak beklenmedik, 0 için bildirilmiş bir enum değeriniz varsa, koleksiyonda her zaman döndürülecektir. Görünüşe göre, yalnızca checkZero doğruysa ve başka bayrak ayarlanmamışsa döndürülmelidir.
dhochee

@dhochee. Katılıyorum. Veya kod oldukça güzel, ancak argümanlar kafa karıştırıcı.
2017

2

Greg'in yukarıdaki cevabına dayanarak, bu aynı zamanda numaralandırmanızda Yok = 0 gibi bir 0 değerine sahip olduğunuz durumu da dikkate alır. Bu durumda, bu değer üzerinde yineleme yapmamalıdır.

public static IEnumerable<Enum> ToEnumerable(this Enum input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
        if (input.HasFlag(value) && Convert.ToInt64(value) != 0)
            yield return value;
}

Numaralandırmadaki tüm bayrakların tüm temel numaralandırma türünü ve Tümü = ~ 0 ve Tümü = EnumValue1 | EnumValue2 | EnumValue3 | ...


1

Enum'dan bir Yineleyici kullanabilirsiniz. MSDN kodundan başlayarak:

public class DaysOfTheWeek : System.Collections.IEnumerable
{
    int[] dayflag = { 1, 2, 4, 8, 16, 32, 64 };
    string[] days = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
    public string value { get; set; }

    public System.Collections.IEnumerator GetEnumerator()
    {
        for (int i = 0; i < days.Length; i++)
        {
            if value >> i & 1 == dayflag[i] {
                yield return days[i];
            }
        }
    }
}

Test edilmedi, bu yüzden bir hata yaptıysam beni aramaktan çekinmeyin. (açıkçası yeniden giren değil.) Önceden değer atamanız veya enum.dayflag ve enum.days kullanan başka bir işleve bölmeniz gerekir. Anahatla bir yere gidebilirsin.


0

Aşağıdaki kod kadar iyi olabilir:

public static string GetEnumString(MyEnum inEnumValue)
{
    StringBuilder sb = new StringBuilder();

    foreach (MyEnum e in Enum.GetValues(typeof(MyEnum )))
    {
        if ((e & inEnumValue) != 0)
        {
           sb.Append(e.ToString());
           sb.Append(", ");
        }
    }

   return sb.ToString().Trim().TrimEnd(',');
}

Yalnızca enum değeri değerde bulunduğunda içeri girer


0

Tüm cevaplar basit bayraklarla iyi çalışıyor, bayraklar birleştirildiğinde muhtemelen sorunlara gireceksiniz.

[Flags]
enum Food
{
  None=0
  Bread=1,
  Pasta=2,
  Apples=4,
  Banana=8,
  WithGluten=Bread|Pasta,
  Fruits = Apples | Banana,
}

Enum değerinin bir kombinasyon olup olmadığını test etmek için muhtemelen bir kontrol eklemeniz gerekir. İhtiyaçlarınızı karşılamak için muhtemelen burada Henk van Boeijen tarafından yayınlanan bir şeye ihtiyacınız olacaktır (biraz aşağı kaydırmanız gerekir)


0

Yeni Enum kısıtlamasını ve dönüştürmeyi önlemek için jenerikleri kullanan uzantı yöntemi:

public static class EnumExtensions
{
    public static T[] GetFlags<T>(this T flagsEnumValue) where T : Enum
    {
        return Enum
            .GetValues(typeof(T))
            .Cast<T>()
            .Where(e => flagsEnumValue.HasFlag(e))
            .ToArray();
    }
}

-1

Bunu doğrudan int türüne dönüştürerek yapabilirsiniz, ancak tür denetimini kaybedersiniz. Bence en iyi yol, önerime benzer bir şey kullanmak. Tüm yol boyunca uygun türü korur. Dönüştürmeye gerek yok. Performansa biraz vuruş katacak olan boks nedeniyle mükemmel değil.

Mükemmel değil (boks), ama hiçbir uyarı olmadan işi yapıyor ...

/// <summary>
/// Return an enumerators of input flag(s)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static IEnumerable<T> GetFlags<T>(this T input)
{
    foreach (Enum value in Enum.GetValues(input.GetType()))
    {
        if ((int) (object) value != 0) // Just in case somebody has defined an enum with 0.
        {
            if (((Enum) (object) input).HasFlag(value))
                yield return (T) (object) value;
        }
    }
}

Kullanımı:

    FileAttributes att = FileAttributes.Normal | FileAttributes.Compressed;
    foreach (FileAttributes fa in att.GetFlags())
    {
        ...
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.