2010-11-05 58 views
38

考慮到這一點:通用擴展方法,如果枚舉包含一個標誌

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

是否可以寫一個通用版本包含了將任何enum而不僅僅是MyEnum工作?

編輯:

這將是我的版本閱讀你的答案後:

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

     return (keysVal & flagVal) == flagVal; 
    } 

剛剛意識到是一個壞主意來檢查我檢查(return (keys & flag) != 0;)的方式,因爲flag參數實際上可能是幾個標誌,常識性事情只有在keys包含所有這些標誌時纔會返回true。此外,我不會檢查空值或甚至確保它們是相同的類型。我可能想要使用不同的類型。

+1

可能是有趣的:http://stackoverflow.com/questions/7244/anyone-know-a-good-workaround-for-the-lack-of-an-enum-generic-constraint – 2010-11-05 18:34:36

+1

它可能是一個好主意標記這與您正在使用的C#版本,因爲最新版本內置此功能。 – chilltemp 2010-11-05 18:42:38

+1

我不明白'Contains'方法如何添加任何內容的實例方法'Enum.HasFlag(枚舉)'。查看該方法的反編譯代碼,您的方法看起來正在做同樣的事情,只需少量的錯誤檢查。 – 2012-10-07 14:31:40

回答

52

我根據這個方法一串的SO &谷歌的搜索,以及通過使用反射器,看看有什麼MS爲.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); 

    } 

} 

注:

  • 它處理空值
  • 是否進行類型檢查
  • 轉換爲ULONG,並且可以處理任何積極的枚舉值。Microsoft cautions反對使用負標誌枚舉反正:

    要小心,如果你定義一個負數的枚舉常因爲許多標誌位置可能被設置爲1的標誌,這可能使你的代碼混亂和鼓勵編碼錯誤。

+0

這不是通用的,而使用負值枚舉值的東西可能會很危險。我會建議投入檢查:: 'Type.GetTypeCode(variable.GetType())== TypeCode.UInt64? Convert.ToUInt64(...):Convert.ToInt64(...)' 是的,它是工作的兩倍,但你更安全。 – 2010-11-05 21:08:59

+1

同意,不是泛型,因爲它不使用泛型。但它適用於所有合理的標誌枚舉。爲了使枚舉枚舉正常工作,所有標誌必須具有相同的符號。負標誌實際上是設置值的2位。因此,儘管在技術上有可能產生負面的標誌,但這種方法只是潛在問題的開始。有可能還有其他枚舉函數會打破負值。使用ulong是我從反映.NET 4 HasFlag函數中學到的一部分。 – chilltemp 2010-11-05 21:34:08

6

不知道,如果你正在使用.NET 4.0或不是,但它與靜態方法Enum.HasFlags()

- 代碼中刪除(接受的解決方案有它的話) -

+0

你怎麼能模仿它?我有4.0,並沒有注意到'Enum.HasFlags()'方法,但現在我只是好奇。 – Juan 2010-11-05 18:25:45

+0

你的代碼不適合我。即使我從第3行中除去多餘的分號並將表達式更改爲'return(keys&flag)== flag',我也會得到「操作符'&'不能應用於'System.Enum'和'System'類型的操作數.Enum' – 2010-11-05 18:28:12

+0

它說「操作符」和「不能用於'System.Enum'和'System.Enum'類型的操作數」 – Juan 2010-11-05 18:28:47

1

這是一件應該工作的例子。

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

不幸的是,沒有一種好方法可以做出這樣的擴展方法。爲了使這個工作,你需要有一個通用的方法,在enum值上運行。不幸的是沒有辦法約束通用參數是一個枚舉

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

最好的,我拿出在以下

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

然而,它在幾個方面有限的

  • 不是類型安全的,因爲沒有需求值和標誌具有相同類型的
  • 假定所有enum VAL你是基於int
  • 原因拳擊發生了一個簡單的位檢查
  • 將拋出如果enull
+0

這真的很危險,不應該使用,因爲的一個對象,然後再一次int,並且e可能爲空,因爲Enum是一個引用類型 – 2010-11-05 21:06:45

+0

@Michael,這是在限制中註明的 – JaredPar 2010-11-05 21:08:25

+0

它沒有聲明它會拋出一個異常,如果e爲null :),但是是的。 – 2010-11-05 21:12:18

2

基本上可以使用現有的擴展方法,BYT使用Enum類型,而不是MyEnum。問題在於它不知道枚舉是否爲標誌,並且不允許&運算符,因此您只需將枚舉值轉換爲數字即可。

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

和良好的措施單元測試:

[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

邊注;從技術上講,類型檢查不必在那裏,因爲我們正在轉換回一個數字,所以如果你想比較兩種不同類型的枚舉,那麼你可以刪除這兩行。 – CodingWithSpike 2010-11-05 19:00:31

4

這是我的做法,這是類型安全的,不會做任何拳或拆箱。如果該類型不是枚舉,則會引發異常。 如果你想將它變成一個公共的靜態方法,你可以使用這種方法,這種方法將被輸入到Enum的方法中,但是它不能作爲擴展方法。 也不需要檢查null,因爲struct contraint也會阻塞可爲null的枚舉。我認爲除了可能用F#或C++/CLI編寫代碼之外,還有很多工作可以改進這些代碼,這樣您就可以對它施加枚舉約束。 的想法是使用表達式目錄樹,將枚舉轉換爲建立一個函數或者長如果任何東西,但基於ULONG枚舉或ulong,然後和他們基本上是生產:: 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  
} 

正如我忘了上面的表達式樹是.NET 4只以下的方法應該工作在.NET 3.5創建同一表達式樹::

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

這個版本應該在編譯.NET 3.5,如果沒有我不明白爲什麼。

+1

有趣的方法,但用您使用的方法來判斷是.NET 4.我們已經確定.NET 4不是一種選擇。無論如何,它都有內置的功能。 – chilltemp 2010-11-05 21:15:58

+0

實際上啊,這裏所有的東西都可以在沒有.Net 4的情況下完成,唯一的一點是,爲了提高效率,我可以保存鑄造旗子的結果,因爲它必須投兩次。這在.Net 3.5中是不允許的,但是,您實際上可以替換flagValueVariable用法和賦值::我將修改答案以支持.net 3.5。 – 2010-11-05 21:21:18

+0

正是我一直在尋找。由於實現不使用裝箱/取消裝箱,這是將其作爲方法擴展使用時最快的方式! – AcidJunkie 2018-01-23 08:06:07

1

我還有另一種方法,我只是用Delegate.CreateDelegate允許在Enum和它們的基礎類型的方法之間進行轉換的事實來快速編譯出來的。以下方法與我之前的回答非常相似,但對於不知道表達式樹語法的人來說,我覺得可能更容易閱讀。基本上我們知道枚舉只有8種可能的基礎類型,所以我們只爲每個可能使用的調用創建一個靜態方法。因爲我要爲我簡潔使用剛巧被命名爲同樣的事情可能的類型代碼values.This方法將在.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

實施HasFlag功能的另一種方式工作,匿名方法爲.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)); 
} 

此擴展方法支持所有可能類型的枚舉(bytesbyteshortushortintuintlongulong)。基本上,該方法檢查給定的枚舉是否已簽名/未簽名,並將該標誌轉換爲枚舉支持類型的最大大小的類型。然後,使用&運算符執行簡單比較。

正如其他文章中所解釋的,我們無法使用枚舉定義泛型類型的約束條件,因爲w開發者可以插入其他枚舉類型或結構,因此無法使用泛型約束條件。所以,我認爲最好不要使用泛型方法。

+0

您的實現比其他實現更健壯,比表達式樹更簡單。我會喜歡兩次。 – 2017-02-09 12:13:52