2010-11-05 11 views
38

Considerando questo:metodo di estensione generico per vedere se un enum contiene un flag

[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; 
    } 
} 

E 'possibile scrivere una versione generica di Contiene che avrebbe funzionato per qualsiasi enum e non solo MyEnum?

Edit:

Questa sarebbe la mia versione dopo aver letto le tue risposte:

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

     return (keysVal & flagVal) == flagVal; 
    } 

appena realizzato è una cattiva idea per controllare il modo in cui stavo controllando (return (keys & flag) != 0;), perché il parametro flag potrebbe essere in realtà più flag e la cosa del buon senso da fare è restituire true solo se keys contiene tutti loro. Inoltre, non verificherei i valori nulli o assicurarmi che siano dello stesso tipo. Potrei volere per utilizzare diversi tipi.

+1

Potrebbe essere di interesse: http://stackoverflow.com/questions/7244/anyone-know-a-good-work-und-for-the-lack-of-an-enum-generic-constraint –

+1

Potrebbe essere un buona idea taggare questo con la versione di C# che stai usando, poiché l'ultima versione ha questa funzione integrata. – chilltemp

+1

Non capisco come il tuo metodo 'Contains' aggiunga qualcosa al metodo istanza integrato' Enum.HasFlag (enum) '. Guardando il codice decompilato per quel metodo, sembra che il tuo metodo stia facendo esattamente la stessa cosa con un po 'meno di controllo degli errori. –

risposta

52

ho basato questo metodo fuori di un gruppo di SO & ricerche di Google, e utilizzando riflettore per vedere cosa MS fatto per il metodo .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); 

    } 

} 

Note:

  • Questo gestisce i null
  • Vuol controllo di tipo
  • converte in ulong, e in grado di gestire qualsiasi valore enum positivo.Microsoft cautions contro l'uso di bandiere enumerazioni negativi in ​​ogni caso:

    Prestare attenzione se si definisce un numero negativo come una bandiera enumerato costante perché molte posizioni di bandiera potrebbe essere impostato a 1, che potrebbe rendere il codice confuso e incoraggiare errori di codifica.

+0

Questo non è generico e lanciare qualcosa che utilizza un valore enum negativo potrebbe essere pericoloso. Suggerirei di introdurre un assegno :: 'Type.GetTypeCode (variable.GetType()) == TypeCode.UInt64? Convert.ToUInt64 (...): Convert.ToInt64 (...) ' Sì, è il doppio del lavoro, ma tu sei molto più sicuro. –

+1

Concordato, non "generico" in quanto non utilizza i generici. Funziona su tutte le enumerazioni di bandiere ragionevoli. Affinché un'enumerazione di bandiere funzioni, tutti i flag devono avere lo stesso segno. E un flag negativo potrebbe effettivamente impostare 2 bit del valore. Quindi, mentre tecnicamente è possibile avere una bandiera negativa, questo metodo è solo l'inizio dei potenziali problemi. È possibile che ci siano altre funzioni enum che si romperanno con valori negativi. L'uso di ulong fa parte di ciò che ho imparato dal riflettere sulla funzione .NET 4 HasFlag. – chilltemp

6

Non sicuro se si utilizza .NET 4.0 o meno, ma viene fornito con il metodo statico Enum.HasFlags().

- Codice Rimosso (la soluzione accettata l'ha già fatto) -

+0

Come potresti emularlo? Ho 4.0 e non ho notato il metodo 'Enum.HasFlags()', ma ora sono solo curioso. – Juan

+0

Il tuo codice non viene compilato per me. Anche se rimuovo il punto e virgola estraneo dalla 3a riga e cambi l'espressione in 'return (keys & flag) == flag', ottengo che" Operator '&' non può essere applicato agli operandi di tipo 'System.Enum' e 'System .Enum '. –

+0

Dice "Operatore' & 'non può essere applicato agli operandi di tipo' System.Enum 'e' System.Enum '". – Juan

1

Questo è un esempio di qualcosa che dovrebbe funzionare.

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

Sfortunatamente non c'è un buon modo per creare un metodo di estensione come questo. Affinché questo funzioni, è necessario disporre di un metodo generico che funzioni sui valori enum. Purtroppo non c'è modo di limitare gli argomenti generici di essere un enum

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

Il migliore che è venuta in mente è la seguente

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

Tuttavia è limitato in vari modi

  • non digitare sicuro perché non v'è alcun obbligo il valore e la bandiera hanno lo stesso tipo
  • presuppone che tutti i enum val sono basati su int.
  • cause di boxe a verificarsi per un semplice controllo po
  • getterà se e è null
+0

Questo è veramente pericoloso e non dovrebbe essere usato perchè del casting per oggetto e poi ancora per int. Anche e potrebbe essere nullo poichè Enum è un tipo di riferimento –

+0

@ Michael, questo è stato notato nelle limitazioni – JaredPar

+0

In nessun luogo si afferma che genererà un'eccezione se e è nullo :), ma sì. –

2

si può sostanzialmente utilizzare il metodo di estensione esistente, utilizzare byt il tipo Enum invece di MyEnum. Il problema è allora che non conosce le enumerazioni sono bandiere e non permetterà all'operatore &, in modo da avere solo per convertire i valori enum ai numeri.

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

E uno unit test per buona misura:

[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

Lato-nota; tecnicamente il tipo di controllo non deve essere lì, poiché stiamo convertendo di nuovo in un numero, quindi se si desidera confrontare 2 diversi tipi di enumerazione, è possibile rimuovere quelle 2 righe. – CodingWithSpike

4

Questo è il mio approccio è SICURO tipo e non fa alcuna boxe o di unboxing. Genera un'eccezione se il tipo non è un enum. Esiste una tecnica si può usare se si vuole trasformarlo in un metodo statico pubblico che verrà digitato a Enum di, ma non può essere un metodo di estensione poi. C'è anche bisogno di verificare la presenza di nulla, come i blocchi di struct per vincoli fuori annullabile enum di pure. Non credo ci sia molto da fare per migliorare questo codice, con l'eccezione forse di scriverlo in F # o C++/CLI in modo che si può mettere un vincolo enum su di esso. L'idea è quella di costruire una funzione tramite alberi di espressione che convertiranno l'enumerazione a uno lungo se tutt'altro che un enum ulong base, o ulong e poi e, in sostanza, la produzione :: 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  
} 

Come ho dimenticato che l'albero di espressione di cui sopra è NET 4 solo il metodo seguente dovrebbe funzionare in .NET 3.5 per creare lo stesso albero di espressione ::

 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(); 
     } 

questa versione dovrebbe compilare in .NET 3.5 e se non lo fa io non riesco a capire perché.

+1

Un approccio interessante, ma a giudicare dai metodi che stai utilizzando è .NET 4. Abbiamo già stabilito che .NET 4 non è un'opzione. Un ha una funzione incorporata per farlo comunque. – chilltemp

+0

Ah in realtà tutto ciò che è qui può essere fatto senza .Net 4 l'unica cosa è che per una minore efficienza, salvi il risultato del lancio della bandiera perché deve essere lanciata due volte. Ciò non è consentito in .Net 3.5, tuttavia, è possibile sostituire effettivamente l'utilizzo flagValueVariable e l'assegnazione :: Revisionerò la risposta per supportare .net 3.5. –

+0

Esattamente stavo cercando. Dal momento che l'implementazione non sta usando boxing/unboxing, questo è il modo più veloce quando lo si utilizza come estensione del metodo! – AcidJunkie

1

Ho un altro approccio qui che ho appena cucinato rapidamente sfruttando il fatto che Delegate.CreateDelegate consente la conversione tra i metodi per Enum del ei loro tipi sottostanti. Il seguente approccio è molto simile alla mia risposta precedente, ma ritengo che potrebbe essere più facile da leggere per le persone che non conoscono la sintassi della struttura delle espressioni. Fondamentalmente sappiamo che Enums ha solo 8 possibili tipi sottostanti, quindi creiamo un metodo statico per ogni chiamata che potrebbe usare. Dal momento che sto andando per brevità io uso i metodi anonimi che è accaduto di essere nominato la stessa cosa che l'approccio possibile typecode values.This lavorerà in 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

Un altro modo di attuare hasFlag funzione per .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)); 
} 

Questo metodo di estensione supporta tutti i possibili tipi di un'enumerazione (byte, sbyte, short, ushort, int, uint, long e ulong). Fondamentalmente, il metodo controlla se l'enumerazione data è firmata/non firmata e converte il flag nel tipo con la dimensione più alta dei tipi supportati per un'enumerazione. Quindi, viene eseguito un semplice confronto utilizzando l'operatore &.

Come spiegato in altri post, non è possibile definire un vincolo del tipo generico con un'enumerazione e non ha senso utilizzare il generico con un vincolo struct, in quanto gli sviluppatori di w potrebbero inserire altri tipi o strutture di enumerazione. Quindi, penso che sia meglio non usare un metodo generico per questo.

+0

L'implementazione è più solida di altre ed è più semplice dell'albero dell'espressione. Avrei votato due volte. –

Problemi correlati