2010-11-05 11 views
38

Teniendo en cuenta esto:método de extensión genérica para ver si una enumeración contiene una bandera

[Flags] 
public enum MyEnum { 
    One = 1, 
    Two = 2, 
    Four = 4, 
    Eight = 8 
} 

public static class FlagsHelper 
{ 
    public static bool Contains(this MyEnum keys, MyEnum flag) 
    { 
     return (keys & flag) != 0; 
    } 
} 

¿Es posible escribir una versión genérica de Contiene eso funcionaría para cualquier enum y no sólo MyEnum?

Editar:

Esto sería mi versión después de leer sus respuestas:

public static bool Contains(this Enum keys, Enum flag) 
    { 
     ulong keysVal = Convert.ToUInt64(keys); 
     ulong flagVal = Convert.ToUInt64(flag); 

     return (keysVal & flagVal) == flagVal; 
    } 

acabo de dar cuenta que es una mala idea para comprobar la forma en que era el check (return (keys & flag) != 0;), debido a que el parámetro flag podría ser en realidad varias banderas y lo que se debe hacer con sentido común es devolver verdadero solo si keys contiene todas ellas. Además, no verificaría valores nulos o incluso me aseguraré de que sean del mismo tipo. Podría querer para usar diferentes tipos.

+1

Puede ser de interés: http://stackoverflow.com/questions/7244/anyone-know-a-good-workaround-for-the-lack-of-an-enum-generic-constraint –

+1

Podría ser un Es una buena idea etiquetar esto con la versión de C# que está utilizando, ya que la última versión tiene esta función incorporada. – chilltemp

+1

No entiendo cómo su método 'Contiene' agrega nada sobre el método de instancia incorporado' Enum.HasFlag (Enum) '. Mirando el código descompilado para ese método, su método parece estar haciendo exactamente lo mismo con un poco menos de comprobación de errores. –

Respuesta

52

basé este método fuera de un montón de SO & búsquedas de Google, y mediante el uso de reflector para ver qué EM lo hizo para el método .NET 4 HasFlags.

public static class EnumExt 
{ 
    /// <summary> 
    /// Check to see if a flags enumeration has a specific flag set. 
    /// </summary> 
    /// <param name="variable">Flags enumeration to check</param> 
    /// <param name="value">Flag to check for</param> 
    /// <returns></returns> 
    public static bool HasFlag(this Enum variable, Enum value) 
    { 
     if (variable == null) 
      return false; 

     if (value == null) 
      throw new ArgumentNullException("value"); 

     // Not as good as the .NET 4 version of this function, but should be good enough 
     if (!Enum.IsDefined(variable.GetType(), value)) 
     { 
      throw new ArgumentException(string.Format(
       "Enumeration type mismatch. The flag is of type '{0}', was expecting '{1}'.", 
       value.GetType(), variable.GetType())); 
     } 

     ulong num = Convert.ToUInt64(value); 
     return ((Convert.ToUInt64(variable) & num) == num); 

    } 

} 

Notas:

  • Este maneja nulos
  • ¿Tiene la verificación de tipos
  • se convierte en una ulong, y puede manejar cualquier valor de enumeración positiva.Microsoft cautions contra el uso de enumeraciones de indicadores negativos de todos modos:

    Tenga cuidado si define un número negativo como un indicador enumerado constante porque muchas posiciones de indicador pueden establecerse en 1, lo que puede hacer que el código sea confuso y alentar los errores de codificación.

+0

Esto no es genérico, y lanzar algo que usa un valor de enum negativo puede ser peligroso. Sugeriría arrojar un cheque :: 'Type.GetTypeCode (variable.GetType()) == TypeCode.UInt64? Convert.ToUInt64 (...): Convert.ToInt64 (...) ' Sí, es el doble de trabajo, pero eres mucho más seguro. –

+1

De acuerdo, no es 'genérico' ya que no usa genéricos. Pero funciona en todas las enumeraciones de banderas razonables. Para que una enumeración de banderas funcione, todas las banderas deben tener el mismo signo. Y una bandera negativa en realidad estaría configurando 2 bits del valor. Entonces, aunque técnicamente es posible tener una bandera negativa, este método es solo el comienzo de los posibles problemas. Es posible que haya otras funciones enum que rompan con los valores negativos. El uso de ulong es parte de lo que aprendí al reflejar la función .NET 4 HasFlag. – chilltemp

6

No estoy seguro si está usando .NET 4.0 o no, pero viene con el método estático Enum.HasFlags().

- Código Eliminado (la solución aceptada lo tiene ya) -

+0

¿Cómo podrías emularlo? Tengo 4.0 y no noté el método 'Enum.HasFlags()', pero ahora solo tengo curiosidad. – Juan

+0

Tu código no compila para mí. Incluso si elimino el punto y coma ajeno de la tercera línea y cambio la expresión a 'return (keys & flag) == flag', obtengo" Operator '&' no se puede aplicar a operandos de tipo 'System.Enum' y 'System .Enum '. –

+0

Dice que' Operator '&' no se puede aplicar a operandos del tipo 'System.Enum' y 'System.Enum' ". – Juan

1

Este es un ejemplo de algo que se debe trabajar.

public static bool IsValid<T>(this T value) 
{ 
    return Enum.IsDefined(value.GetType(), value); 
} 
2

Lamentablemente, no existe una buena forma de hacer un método de extensión como este. Para que esto funcione, debe tener un método genérico que opere en los valores enum. Desafortunadamente, no hay manera de limitar argumentos genéricos para ser una enumeración

// Ilegal 
public static bool Contains<T>(this T value, T flag) where T : enum { 
    ... 
} 

los mejores que he ocurre es lo siguiente

public static bool HasFlag<T>(this System.Enum e, T flag) 
{ 
    var intValue = (int)(object)e; 
    var intFlag = (int)(object)flag; 
    return (intValue & intFlag) != 0; 
} 

Sin embargo, es limitado en varias formas

  • No escriba seguro porque no hay ningún requisito, el valor y la bandera tienen el mismo tipo
  • Supone que todos enum val ues son int basado.
  • Causas de boxeo para producir una revisión de poco simple
  • tirará si e es null
+0

Esto es realmente peligroso y no debe usarse porque de la conversión a objeto y que una vez más a int. También e podría ser nulo, ya que Enum es un tipo de referencia. –

+0

@Michael, esto se notó en las limitaciones. – JaredPar

+0

En ninguna parte indica que arrojará una excepción si e es nulo :), pero sí. –

2

básicamente se puede utilizar el método de extensión existente, utilice el tipo byt Enum en lugar de MyEnum. El problema entonces es que no sabe que las enumeraciones son banderas y no permitirá el operador &, por lo que solo tiene que convertir los valores enum en números.

public static bool Contains(this Enum keys, Enum flag) 
    { 
     if (keys.GetType() != flag.GetType()) 
      throw new ArgumentException("Type Mismatch"); 
     return (Convert.ToUInt64(keys) & Convert.ToUInt64(flag)) != 0; 
    } 

Y una prueba de unidad para la buena medida:

[TestMethod] 
    public void TestContains() 
    { 
     var e1 = MyEnum.One | MyEnum.Two; 
     Assert.IsTrue(e1.Contains(MyEnum.Two)); 

     var e2 = MyEnum.One | MyEnum.Four; 
     Assert.IsFalse(e2.Contains(MyEnum.Two)); 
    } 
+0

Nota lateral; Técnicamente, la verificación de tipo no tiene que estar allí, ya que estamos convirtiendo de nuevo a un número, por lo que si quiere comparar 2 tipos diferentes de enumeraciones, entonces puede eliminar esas 2 líneas. – CodingWithSpike

4

Este es mi enfoque se trata de un tipo seguro y no hace ningún boxeo o unboxing. Lanza una excepción si el tipo no es una enumeración. Existe una técnica que puede usar si desea convertirla en un método público estático que se escribirá en Enum, pero no puede ser un método de extensión. . Tampoco es necesario comprobar si hay nulo, ya que struct contrôt también bloquea las enum anulables. No creo que haya mucho por hacer para mejorar este código, con la excepción de tal vez escribirlo en F # o C++/CLI para que pueda poner una restricción enum sobre él. La idea es construir una función utilizando los árboles de expresión que convertirán a la enumeración sea mucho tiempo si su algo más que una enumeración basada ulong o ulong y luego y ellos, para producir esencialmente :: return value & flag == flag

public static class EnumExtensions 
{ 
    #region Public Static Methods  
    /// <summary> 
    /// Determines whether the specified value has flags. Note this method is up to 60 times faster 
    /// than the one that comes with .NET 4 as it avoids any explict boxing or unboxing. 
    /// </summary> 
    /// <typeparam name="TEnum">The type of the enum.</typeparam> 
    /// <param name="value">The value.</param> 
    /// <param name="flag">The flag.</param> 
    /// <returns> 
    /// <c>true</c> if the specified value has flags; otherwise, <c>false</c>. 
    /// </returns> 
    /// <exception cref="ArgumentException">If TEnum is not an enum.</exception> 
    public static bool HasFlags<TEnum>(this TEnum value, TEnum flag) where TEnum:struct,IComparable,IConvertible,IFormattable 
    { 
    return EnumExtensionsInternal<TEnum>.HasFlagsDelegate(value, flag); 
    } 
    #endregion Public Static Methods  

    #region Nested Classes  

    static class EnumExtensionsInternal<TEnum> where TEnum : struct,IComparable, IConvertible, IFormattable 
    { 
    #region Public Static Variables  
    /// <summary> 
    /// The delegate which determines if a flag is set. 
    /// </summary> 
    public static readonly Func<TEnum, TEnum, bool> HasFlagsDelegate = CreateHasFlagDelegate(); 
    #endregion Public Static Variables  

    #region Private Static Methods  
    /// <summary> 
    /// Creates the has flag delegate. 
    /// </summary> 
    /// <returns></returns> 
    private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate() 
    { 
    if(!typeof(TEnum).IsEnum) 
    { 
    throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name); 
    } 
    ParameterExpression valueExpression = Expression.Parameter(typeof(TEnum)); 
    ParameterExpression flagExpression = Expression.Parameter(typeof(TEnum)); 
    ParameterExpression flagValueVariable = Expression.Variable(Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long)); 
    Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
     Expression.Block(
     new[] { flagValueVariable }, 
     Expression.Assign(
      flagValueVariable, 
      Expression.Convert(
      flagExpression, 
      flagValueVariable.Type 
     ) 
     ), 
     Expression.Equal(
      Expression.And(
      Expression.Convert(
       valueExpression, 
       flagValueVariable.Type 
      ), 
      flagValueVariable 
     ), 
      flagValueVariable 
     ) 
    ), 
     valueExpression, 
     flagExpression 
    ); 
    return lambdaExpression.Compile(); 
    } 
    #endregion Private Static Methods  
    } 
    #endregion Nested Classes  
} 

Mientras olvidó que el árbol de expresión anterior es .NET 4 solamente la siguiente método debería funcionar en .NET 3.5 para crear el mismo árbol de expresión ::

 private static Func<TEnum, TEnum, bool> CreateHasFlagDelegate2() 
     { 
      if(!typeof(TEnum).IsEnum) 
      { 
       throw new ArgumentException(string.Format("{0} is not an Enum", typeof(TEnum)), typeof(EnumExtensionsInternal<>).GetGenericArguments()[0].Name); 
      } 
      ParameterExpression valueExpression = Expression.Parameter(
        typeof(TEnum), 
        typeof(TEnum).Name 
      ); 
      ParameterExpression flagExpression = Expression.Parameter(
        typeof(TEnum), 
        typeof(TEnum).Name 
      ); 
      var targetType = Type.GetTypeCode(typeof(TEnum)) == TypeCode.UInt64 ? typeof(ulong) : typeof(long); 
      Expression<Func<TEnum, TEnum, bool>> lambdaExpression = Expression.Lambda<Func<TEnum, TEnum, bool>>(
          Expression.Equal(
            Expression.And(
              Expression.Convert(
                valueExpression, 
                targetType 
              ), 
              Expression.Convert(
               flagExpression, 
               targetType 
              ) 
            ), 
            Expression.Convert(
             flagExpression, 
             targetType 
            ) 
          ), 
        valueExpression, 
        flagExpression 
      ); 
      return lambdaExpression.Compile(); 
     } 

esta versión debería compilar en .NET 3.5 y si no lo hace I no puedo entender por qué

+1

Enfoque interesante, pero a juzgar por los métodos que está utilizando es .NET 4. Ya hemos determinado que .NET 4 no es una opción. Y tiene una función incorporada para hacer esto de todos modos. – chilltemp

+0

Ah, en realidad, todo lo que se puede hacer aquí se puede hacer sin .Net 4, lo único es que para menor eficiencia, guardo el resultado de lanzar la bandera ya que tiene que emitirse dos veces. Esto no está permitido en .Net 3.5, sin embargo, puedes reemplazar el uso de flagValueVariable y la asignación :: Revisaré la respuesta para admitir .net 3.5. –

+0

Exactamente estaba buscando. ¡Como la implementación no usa boxing/unboxing, esta es la manera más rápida cuando se usa como una extensión de método! – AcidJunkie

1

Tengo otro enfoque aquí que acabo de preparar rápidamente utilizando el hecho de que Delegate.CreateDelegate permite la conversión entre métodos para Enum y sus tipos subyacentes. El siguiente enfoque es muy parecido a mi respuesta anterior, pero creo que podría ser más fácil de leer para las personas que no conocen la sintaxis del árbol de expresiones. Básicamente, sabemos que los Enums solo tienen 8 tipos subyacentes posibles, por lo que solo creamos un método estático para cada llamada que podría usar. Ya que voy por razones de brevedad utilizo métodos anónimos, que pasó a ser nombrado el mismo que el enfoque de la posible código_de_tipo values.This funcionará en .Net 3.5 ::

public static class EnumHelper 
{ 
    delegate bool HasFlag<T>(T left,T right); 
    static readonly HasFlag<Byte> Byte = (x,y)=> (x&y) ==y; 
    static readonly HasFlag<SByte> Sbyte = (x,y)=> (x&y) ==y; 
    static readonly HasFlag<Int16> Int16 = (x,y)=> (x&y) ==y; 
    static readonly HasFlag<UInt16> UInt16 = (x,y)=> (x&y) ==y; 
    static readonly HasFlag<Int32> Int32 = (x,y)=> (x&y) ==y; 
    static readonly HasFlag<UInt32> UInt32 = (x,y)=> (x&y) ==y; 
    static readonly HasFlag<Int64> Int64 = (x,y)=> (x&y) ==y; 
    static readonly HasFlag<UInt64> UInt64 = (x,y)=> (x&y) ==y; 

    public static bool HasFlags<TEnum>(this TEnum @enum,TEnum flag) where TEnum:struct,IConvertible,IComparable,IFormattable 
    { 
     return Enum<TEnum>.HasFlag(@enum,flag); 
    } 
    class Enum<TEnum> where TEnum:struct,IConvertible,IComparable,IFormattable 
    { 
     public static HasFlag<TEnum> HasFlag = CreateDelegate(); 
     static HasFlag<TEnum> CreateDelegate() 
     { 
      if (!typeof(TEnum).IsEnum) throw new ArgumentException(string.Format("{0} is not an enum", typeof(TEnum)), typeof(Enum<>).GetGenericArguments()[0].Name); 
      var delegateName = Type.GetTypeCode(typeof(TEnum)).ToString(); 
      var @delegate = typeof(EnumHelper).GetField(delegateName,BindingFlags.Static | BindingFlags.NonPublic).GetValue(null) as Delegate; 
      return Delegate.CreateDelegate(typeof(HasFlag<TEnum>), @delegate.Method) as HasFlag<TEnum>; 
     } 
    } 
} 
2

Otra forma de implementar la función HasFlag para .NET Framework 3.5.

public static bool HasFlag(this Enum e, Enum flag) 
{ 
    // Check whether the flag was given 
    if (flag == null) 
    { 
     throw new ArgumentNullException("flag"); 
    } 

    // Compare the types of both enumerations 
    if (e.GetType() != (flag.GetType())) 
    { 
     throw new ArgumentException(string.Format(
      "The type of the given flag is not of type {0}", e.GetType()), 
      "flag"); 
    } 

    // Get the type code of the enumeration 
    var typeCode = e.GetTypeCode(); 

    // If the underlying type of the flag is signed 
    if (typeCode == TypeCode.SByte || typeCode == TypeCode.Int16 || typeCode == TypeCode.Int32 || 
     typeCode == TypeCode.Int64) 
    { 
     return (Convert.ToInt64(e) & Convert.ToInt64(flag)) != 0; 
    } 

    // If the underlying type of the flag is unsigned 
    if (typeCode == TypeCode.Byte || typeCode == TypeCode.UInt16 || typeCode == TypeCode.UInt32 || 
     typeCode == TypeCode.UInt64) 
    { 
     return (Convert.ToUInt64(e) & Convert.ToUInt64(flag)) != 0; 
    } 

    // Unsupported flag type 
    throw new Exception(string.Format("The comparison of the type {0} is not implemented.", e.GetType().Name)); 
} 

Este método extensión es compatible con todos los tipos posibles para una enumeración (byte, sbyte, short, ushort, int, uint, long y ulong). Básicamente, el método verifica si la enumeración dada está firmada/no firmada y convierte el indicador al tipo con el mayor tamaño de los tipos admitidos para una enumeración. Luego, se realiza una comparación simple usando el operador &.

Como se explica en otras publicaciones, no podemos definir una restricción del tipo genérico con una enumeración y no tiene sentido usar generic con una restricción struct, porque los desarrolladores podrían insertar otras estructuras o tipos de enumeraciones. Entonces, creo que es mejor no usar el método genérico para eso.

+0

Su implementación es más robusta que otras, y es más simple que el árbol de expresiones. Yo votaría dos veces. –

Cuestiones relacionadas