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

Jua*_*uan 40 c# enums

考虑到这一点:

[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;
    }
}
Run Code Online (Sandbox Code Playgroud)

是否有可能编写一个适用于任何产品的通用版本的Contains 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;
    }
Run Code Online (Sandbox Code Playgroud)

刚刚意识到检查我正在检查(return (keys & flag) != 0;)的方式是一个坏主意,因为flag参数实际上可能是几个标志,而常识要做的只有在keys包含所有标志时才返回true .此外,我不会检查空值或甚至确保它们是相同的类型.我可能想要使用不同的类型.

chi*_*emp 54

我将此方法基于一堆SO和Google搜索,并使用反射器来查看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);

    }

}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 这会处理空值
  • 进行类型检查
  • 转换为ulong,并且可以处理任何正的枚举值. Microsoft警告不要使用负标记枚举:

    如果将负数定义为标志枚举常量,请谨慎使用,因为许多标志位置可能设置为1,这可能会使您的代码混淆并鼓励编码错误.

  • 同意,而不是'通用',因为它不使用泛型.但它适用于所有合理的标志枚举.要使标志枚举起作用,所有标志必须具有相同的符号.负标志实际上将设置值的2位.因此,虽然技术上可能有负面标志,但这种方法只是潜在问题的开始.有可能还有其他枚举函数会破坏负值.使用ulong是我从反映.NET 4 HasFlag函数中学到的东西的一部分. (3认同)

Jus*_*ner 7

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

- 删除了代码(已接受的解决方案已经删除) -


Mic*_*l B 5

这是我的方法,这是类型安全,不做任何装箱或拆箱.如果类型不是枚举,则抛出异常.如果你想将它变成一个将被输入Enum的公共静态方法,你可以使用一种技术,但它不能是一种扩展方法.也没有必要检查null,因为struct contraint也阻止了可以为空的枚举.我不认为要改进这些代码还有很多工作要做,除了可以用F#或C++/CLI编写代码,以便你可以对它进行枚举约束.我的想法是使用表达式树构建一个函数,它将枚举转换为long,如果它只是一个基于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 
 }
Run Code Online (Sandbox Code Playgroud)

由于我忘记了上面的表达式树是.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();
        }
Run Code Online (Sandbox Code Playgroud)

这个版本应该在.NET 3.5中编译,如果不是,我无法理解为什么.