2011-04-04 17 views
7

que tienen una enumeración banderas define así:Banderas de impresión Enum como banderas separadas

[Flags] 
public enum MyEnum 
{ 
    None =  0x00, 
    Choice1 = 0x01, 
    Choice2 = 0x02, 
    Choice3 = 0x04, 
    Default = Choice1 | Choice2, 
    All =  Default | Choice3 
} 

Me gustaría una manera de imprimir las cuales se incluyen banderas en MyEnum.Default. En este caso, me gustaría que la salida fuera algo así como "Choice1, Choice2".

El problema con la simple impresión MyEnum.Default.ToString() es que la salida sería "por defecto" cuando quiero "Choice1, Choice2".

Aquí es una opción, pero si he usado esta tendría que actualizar la impresión cada vez que cambiaba la enumeración.

((StudyData.Choice1 & StudyData.Default) == StudyData.Choice1 ? StudyData.Choice1.ToString() : "") + ", " + 
((StudyData.Choice2 & StudyData.Default) == StudyData.Choice2 ? StudyData.Choice2.ToString() : "") + ", " + 
((StudyData.Choice3 & StudyData.Default) == StudyData.Choice3 ? StudyData.Choice3.ToString() : "") 

¿Alguien tiene una manera más clara de hacer esto? Idealmente, me gustaría obtener una forma de imprimir los indicadores incluidos en MyEnum.Default sin tener que cambiar el código de impresión cada vez que agregué un nuevo marcador o cambié el valor predeterminado.

Gracias!

Respuesta

10

Utilizando los métodos de extensión que he escrito here sobre una cuestión relacionada, esto debería ser sencillo:

var value = MyEnum.Default; 
var str = String.Join(", ", value.GetIndividualFlags()); 
// "Choice1, Choice2" 

y aquí está la métodos de extensión:

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; 
     } 
    } 
} 
+1

Agradable ... y descaradamente robado para referencia futura. –

+0

Tenga en cuenta que si su enumeración tiene valores negativos (por ejemplo, con el marco de extensibilidad de Visual Studio, Microsoft.VisualStudio.Shell.Interop._VSRDTFLAGS), entonces el uso de ulong y ToUInt64 aquí causará problemas. Si reemplazas con long y ToInt64, manejará enumeraciones con valores negativos. – Nerdtron

3

Adorne su enumeración con [FlagsAttribute]. Hace casi exactamente lo que está buscando:

[FlagsAttribute] 
public enum FooNum 
{ 
    foo = 0, 
    bar = 1, 
    lulz = 2, 
    borkbork = 4 
} 

FooNum f = FooNum.bar | FooNum.borkbork; 

Debug.WriteLine(f.ToString()); 

debería darle:

bar, borkbork

+0

Ups! Olvidé incluir FlagsAttribute en mi copy-paste. Voy a arreglar la pregunta original. Gracias por la respuesta rápida y perdón por la confusión ... – Brian

+2

Ohhhh ... Veo lo que estás haciendo ahora. El problema con este es incluso si construye manualmente el 'Default' con' yourEnum = yourEnum.Choice1 | yourEnum.Choice2' TODAVÍA obtendrás 'yourEnum.Default' cuando lo empujes a una cadena. ¿Cuánto necesitas las definiciones Default y All en la propia enumeración? Vas a tener que caminar las puntas y tirar de ellas por separado como ya has descubierto. Va a ser un poco desordenado sin importar nada. Una opción diferente sería almacenar sus valores predeterminados como su propio FooNum. Sin embargo, eso es complicado a su manera ... –

1
using System; 
using System.Collections.Generic; 

using System.Text; 

namespace printStar 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 


      Console.WriteLine("Enter the value "); 
      int k = int.Parse(Console.ReadLine()); 
      int n = k - 1; 
      int x = 2 * (k - 1) + 1; 

      for (int p = 0; p <= n; p++) 
      { 
       for (int j = k - 1; j >= 0; j--) 
       { 
        Console.Write(" "); 
       } 

       for (int i = 0; i <= (x - 2 * (k - 1)); i++) 
       { 
        if (i % 2 == 1) 
        { 
         Console.Write("*"); 
        } 
        else 
        { 
         Console.Write(" "); 
        } 
       } 

       Console.WriteLine(); 
       k--; 
      } 
      Console.ReadLine(); 
     } 
    } 
} 
1

He resuelto esto en el, código más claro más corto posible que espero que se desempeña bien, aunque no es el boxeo en un par de lugares. Utilizando el tipo de ejemplo:

MyEnum e = MyEnum.Choice1 | MyEnum.Choice2; 
string s = FlagsEnumToString<MyEnum>(e); // Returns "Choice1, Choice2" 

Esta es la forma en que está implementado:

const string Separator = ", "; 

public static string FlagsEnumToString<T>(Enum e) 
{ 
    var str = new StringBuilder(); 

    foreach (object i in Enum.GetValues(typeof(T))) 
    { 
     if (IsExactlyOneBitSet((int) i) && 
      e.HasFlag((Enum) i)) 
     { 
      str.Append((T) i + Separator); 
     } 
    } 

    if (str.Length > 0) 
    { 
     str.Length -= Separator.Length; 
    } 

    return str.ToString(); 
} 

static bool IsExactlyOneBitSet(int i) 
{ 
    return i != 0 && (i & (i - 1)) == 0; 
} 

Algunos comentarios pueden venir y los he puesto en primer lugar:

Tengo que llamar ¿Su método proporciona variables de tipo y?

Porque esto no se puede hacer con un argumento genérico T implícitamente. T no se puede convertir a Enum para usar con HasFlag. No, tampoco usa where T : struct, IConvertible.

¿El foreach también usa object?

Sí, también para poder emitir.Solo object se puede convertir a los otros tipos T, int, Enum.

creo que esto puede ser optimizado mediante fundición a int dentro del bucle una vez con una variable temporal.

Creo que sí. Este código fue escrito así para mayor claridad. Así que sí hazlo y deshacerte de esas llamadas HasFlag si quieres.

Creo que todavía se puede utilizar como variable de Enumforeach y ahorrar en el casting.

No, porque se necesita un yeso para T y que sólo se puede hacer desde object. Puede haber "mejores" soluciones, pero esta es ciertamente la más corta y clara.

1

impresión por la declaración de LINQ sola:

var names = Enum.GetValues(typeof(MyEnum)) 
    .Cast<MyEnum>() 
    .Where(a => (values & a) == a) 
    .Select(a => a.ToString()) 
    .Aggregate((current, next) => current + ", " + next); 

Versión actualizada para imprimir sólo valores definidos de forma explícita:

var values = MyEnum.All; 

var allAttrs = Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>(); 

var names = allAttrs 
    // leave only explicitly defined and not zero values 
    .Where(attr => allAttrs.Count(a => a != 0 && (attr & a) == a) == 1) 
    .Where(a => (values & a) == a) 
    .Select(a=>a.ToString()) 
    .Aggregate((current, next) => current + ", " + next); 

Console.WriteLine(names); // Choice1, Choice2, Choice3 
Cuestiones relacionadas