查看枚举是否包含标志的通用扩展方法

发布于 2024-10-01 00:49:48 字数 860 浏览 4 评论 0原文

考虑到这一点:

[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 的 Contains 通用版本?

编辑:

在阅读您的答案后,这将是我的版本:

    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。另外,我不会检查空值,甚至不会确保它们是相同的类型。我可能想要使用不同的类型。

Considering this:

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

Is it possible to write a generic version of Contains that would work for any enum and not just MyEnum?

Edit:

This would be my version after reading your answers:

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

        return (keysVal & flagVal) == flagVal;
    }

Just realized is a bad idea to check the way I was checking (return (keys & flag) != 0;), because the flag parameter might be actually several flags and the common sense thing to do is return true only if keys contains all of them. Also, I wouldn't check for null values or even make sure they are the same type. I might want to use different types.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

暖阳 2024-10-08 00:49:48

我的这个方法基于一堆 SO & Google 搜索,并使用 Reflector 来查看 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);

    }

}

注意:

  • 这处理 null
  • 进行类型检查
  • 转换为 ulong,并且可以处理任何正枚举值。 Microsoft 警告无论如何不要使用负标志枚举:

    <块引用>

    如果您将负数定义为标志枚举常量,请务必小心,因为许多标志位置可能会设置为 1,这可能会使您的代码变得混乱并导致编码错误。

I based this method off of a bunch of SO & Google searches, and a by using reflector to see what MS did for the .NET 4 HasFlags method.

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

    }

}

Notes:

  • This handles nulls
  • Does type checking
  • Converts to a ulong, and can handle any positive enum value. Microsoft cautions against the use of negative flags enumerations anyway:

    Use caution if you define a negative number as a flag enumerated constant because many flag positions might be set to 1, which might make your code confusing and encourage coding errors.

深白境迁sunset 2024-10-08 00:49:48

不确定您是否使用 .NET 4.0,但它带有静态方法 Enum.HasFlags()

-- 代码已删除(已接受的解决方案已包含)--

Not sure if you're using .NET 4.0 or not, but it comes with the static method Enum.HasFlags().

-- Code Removed (the accepted solution has it already) --

终陌 2024-10-08 00:49:48

这是我的方法,它是类型安全的,不会进行任何装箱或拆箱。如果类型不是枚举,它将引发异常。
如果您想将其转换为将类型化为 Enum 的公共静态方法,则可以使用一种技术,但它不能是扩展方法。
也不需要检查 null,因为结构约束也阻止了可为 null 的枚举。我认为没有太多工作可以改进此代码,除了可以用 F# 或 C++/CLI 编写它以便可以对其施加枚举约束之外。
这个想法是使用表达式树构建一个函数,如果它不是基于 ulong 的枚举,则将枚举转换为 long,或者 ulong 然后和它们,本质上产生:
返回值& 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 中编译,如果不编译我不明白为什么。

This is my approach this is Type safe and doesn't do any boxing or unboxing. It throws an exception if the type is not an enum.
There is a technique you can use if you want to turn it into a public static method that will be typed to Enum's, but it can't be an extension method then.
There is also no need to check for null, as the struct contraint blocks out nullable enum's as well. I don't think there is much to be done to improve this code, with the exception of maybe writing it in F# or C++/CLI so that you can put an enum constraint on it.
The idea is to build a function using expression trees that will convert the enum to either long if its anything but a ulong based enum, or ulong and then and them, essentially producing::
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 
 }

As I forgot that the expression tree above is .NET 4 only the following method should work in .NET 3.5 to create the same expression tree::

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

this version should compile in .NET 3.5 and if it doesn't I can't understand why.

弥繁 2024-10-08 00:49:48

不幸的是,没有一个好的方法来制作这样的扩展方法。为了使其工作,您需要有一个对 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 值基于 int
  • 导致简单位检查发生装箱
  • 如果 enull 则抛出异常

Unfortunately no there is not a good way to make an extension method like this. In order for this to work you'd need to have a generic method which operated on enum values. Unfortunately there is no way to constrain generic arguments to be an enum

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

The best I've come up with is the following

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

However it's limited in several ways

  • Not type safe because there is no requirement the value and the flag have the same type
  • Assumes that all enum values are int based.
  • Causes boxing to occur for a simple bit check
  • Will throw if e is null
欢你一世 2024-10-08 00:49:48

您基本上可以使用现有的扩展方法,方法是使用 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));
    }

You can basically use your existing extension method, byt use the Enum type instead of MyEnum. The problem then is that it doesn't know the enums are flags and won't allow the & operator, so you just have to convert the enum values to numbers.

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

And a unit test for good measure:

    [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));
    }
生死何惧 2024-10-08 00:49:48

我在这里有另一种方法,我只是利用 Delegate.CreateDelegate 允许 Enum 及其底层类型的方法之间的转换这一事实快速地编写出来。下面的方法很像我之前的答案,但我觉得对于不了解表达式树语法的人来说可能更容易阅读。基本上我们知道 Enums 只有 8 种可能的底层类型,因此我们只需为它可以使用的每个调用创建一个静态方法。为了简洁起见,我使用了匿名方法,这些方法的名称恰好与可能的类型代码值相同。这种方法将在 .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>;
        }
    }
}

I have another approach here that I just cooked up quickly using the fact that Delegate.CreateDelegate allows conversion between methods for Enum's and their underlying types. The following approach is much like my previous answer but I feel might be easier to read for people who don't know expression tree syntax. Basically we know that Enums only have 8 possible underlying types, and so we just create a static method for each call it could use. Since I'm going for brevity I use anonymous methods which happened to be named the same thing as the possible typecode values.This approach will work 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>;
        }
    }
}
憧憬巴黎街头的黎明 2024-10-08 00:49:48

为 .NET Framework 3.5 实现 HasFlag 函数的另一种方法。

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

此扩展方法支持枚举的所有可能类型(bytesbyteshortushort>intuintlongulong)。基本上,该方法检查给定的枚举是否有符号/无符号,并将标志转换为枚举支持的类型中具有最大大小的类型。然后,使用 & 运算符执行简单的比较。

正如其他帖子中所解释的,我们无法使用枚举定义泛型类型的约束,并且使用带有 struct 约束的泛型是没有意义的,因为开发人员可以插入其他枚举类型或结构然后。所以,我认为最好不要使用泛型方法。

Another way of implementing HasFlag function for the .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));
}

This extension method supports all the possible types for an enumeration (byte, sbyte, short, ushort, int, uint, long and ulong). Basically, the method checks if the given enumeration is signed/unsigned and converts the flag to the type with the highest size of the supported types for an enumeration. Then, a simple comparison is performed using the & operator.

As explained in other posts, we cannot define a constraint of the generic type with an enumeration and it doesn't make sense to use generic with a struct constraint, because w developers could insert other enumerations types or structures then. So, I think it's better to not use generic method for that.

丘比特射中我 2024-10-08 00:49:48

这是一个非常高性能、非装箱、免分配、安全(即不受 unsafe 关键字影响)、无分支的解决方案。

它的工作原理是将枚举值重新解释为 ulong,无论其类型或大小如何。它通过将 ulong 值 0 写入堆栈、将这些字节重新解释为一个或多个 TEnum 的序列、写入 TEnum 值来实现此目的到位置[0],然后读回ulong值。

public static class EnumHelper
{
    static EnumHelper()
    {
        // Required to get correct behavior in GetNumericValue, where we overlap the enum type with a ulong, left-aligned
        if (!BitConverter.IsLittleEndian)
            throw new NotSupportedException("This type is only supported on little-endian architectures.");
    }
        
    public static bool HasFlag<T>(T subject, T flag)
        where T : unmanaged, Enum
    {
        var numericSubject = GetNumericValue(subject);
        var numericFlag = GetNumericValue(flag);

        return (numericSubject & numericFlag) == numericFlag;
    }

    /// <summary>
    /// <para>
    /// Returns the numeric value of the given <paramref name="enumValue"/>.
    /// </para>
    /// <para>
    /// The resulting <see cref="ulong"/> can be cast to the intended integral type, even if it is a signed type.
    /// </para>
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ulong GetNumericValue<T>(T enumValue)
        where T : unmanaged, Enum
    {
        Span<ulong> ulongSpan = stackalloc ulong[] { 0UL };
        Span<T> span = MemoryMarshal.Cast<ulong, T>(ulongSpan);

        span[0] = enumValue;

        return ulongSpan[0];
    }
}

Here is a very performant, non-boxing, allocation-free, safe (i.e. free from the unsafe keyword), branchless solution.

It works by reinterpreting the enum value as a ulong, regardless of its type or size. It achieves this by writing ulong value 0 onto the stack, reinterpreting those bytes as a sequence of one or more TEnums, writing the TEnum value into location [0], and then reading back the ulong value.

public static class EnumHelper
{
    static EnumHelper()
    {
        // Required to get correct behavior in GetNumericValue, where we overlap the enum type with a ulong, left-aligned
        if (!BitConverter.IsLittleEndian)
            throw new NotSupportedException("This type is only supported on little-endian architectures.");
    }
        
    public static bool HasFlag<T>(T subject, T flag)
        where T : unmanaged, Enum
    {
        var numericSubject = GetNumericValue(subject);
        var numericFlag = GetNumericValue(flag);

        return (numericSubject & numericFlag) == numericFlag;
    }

    /// <summary>
    /// <para>
    /// Returns the numeric value of the given <paramref name="enumValue"/>.
    /// </para>
    /// <para>
    /// The resulting <see cref="ulong"/> can be cast to the intended integral type, even if it is a signed type.
    /// </para>
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ulong GetNumericValue<T>(T enumValue)
        where T : unmanaged, Enum
    {
        Span<ulong> ulongSpan = stackalloc ulong[] { 0UL };
        Span<T> span = MemoryMarshal.Cast<ulong, T>(ulongSpan);

        span[0] = enumValue;

        return ulongSpan[0];
    }
}
你列表最软的妹 2024-10-08 00:49:48

这是一个应该有效的例子。

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

This is an example of something that should work.

public static bool IsValid<T>(this T value)
{
    return Enum.IsDefined(value.GetType(), value);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文