【问题标题】:Extend Enum with flag methods?使用标志方法扩展枚举?
【发布时间】:2012-02-09 08:39:40
【问题描述】:

我找到了关于如何创建扩展方法以从按位枚举中读取单个值的好例子。但是现在 C# 4 添加了 HasFlag 方法,它们真的不需要了。

我认为真正有用的是对 SET 单个标志的扩展!
我有很多情况需要单独设置标志值。
我想要一个带有这个签名的扩展方法:

enumVariable.SetFlag(EnumType.SingleFlag, true);

或者可能:

enumVariable.SetFlag<EnumType>(EnumType.SingleFlag, true);

【问题讨论】:

  • 为什么需要它?像 enumVariable = enumVariable | 这样的表达式EnumType.SingleFlag;更短更容易阅读,尤其是当您需要设置多个标志时...

标签: c# enums extension-methods bit-manipulation bitflags


【解决方案1】:

今天我在http://hugoware.net/blog/enums-flags-and-csharp 上找到了解决方案。谢谢雨果!运行良好的优秀代码。我稍微调整了一下,并将其添加到我现有的 EnumExtender 中:

public static class EnumExtender
{
    /// <summary>
    /// Adds a flag value to enum.
    /// Please note that enums are value types so you need to handle the RETURNED value from this method.
    /// Example: myEnumVariable = myEnumVariable.AddFlag(CustomEnumType.Value1);
    /// </summary>
    public static T AddFlag<T>(this Enum type, T enumFlag)
    {
        try
        {
            return (T)(object)((int)(object)type|(int)(object)enumFlag);
        }
        catch(Exception ex)
        {
            throw new ArgumentException(string.Format("Could not append flag value {0} to enum {1}",enumFlag, typeof(T).Name), ex);
        }
    }

    /// <summary>
    /// Removes the flag value from enum.
    /// Please note that enums are value types so you need to handle the RETURNED value from this method.
    /// Example: myEnumVariable = myEnumVariable.RemoveFlag(CustomEnumType.Value1);
    /// </summary>
    public static T RemoveFlag<T>(this Enum type, T enumFlag)
    {
        try
        {
            return (T)(object)((int)(object)type & ~(int)(object)enumFlag);
        }
        catch (Exception ex)
        {
            throw new ArgumentException(string.Format("Could not remove flag value {0} from enum {1}", enumFlag, typeof(T).Name), ex);
        }
    }

    /// <summary>
    /// Sets flag state on enum.
    /// Please note that enums are value types so you need to handle the RETURNED value from this method.
    /// Example: myEnumVariable = myEnumVariable.SetFlag(CustomEnumType.Value1, true);
    /// </summary>
    public static T SetFlag<T>(this Enum type, T enumFlag, bool value)
    {
        return value ? type.AddFlag(enumFlag) : type.RemoveFlag(enumFlag);
    }

    /// <summary>
    /// Checks if the flag value is identical to the provided enum.
    /// </summary>
    public static bool IsIdenticalFlag<T>(this Enum type, T enumFlag)
    {
        try
        {
            return (int)(object)type == (int)(object)enumFlag;
        }
        catch
        {
            return false;
        }
    }

    /// <summary>
    /// Convert provided enum type to list of values.
    /// This is convenient when you need to iterate enum values.
    /// </summary>
    public static List<T> ToList<T>()
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException();
        var values = Enum.GetNames(typeof(T));
        return values.Select(value => value.ToEnum<T>()).ToList();
    }

    /// <summary>
    /// Present the enum values as a comma separated string.
    /// </summary>
    public static string GetValues<T>()
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException();
        var values = Enum.GetNames(typeof(T));
        return string.Join(", ", values);
    }

}

【讨论】:

  • 这不是类型安全的。当然还有一点:如果一个解决方案对您有用,那么当您稍后找到另一个解决方案时,没有理由从帖子中删除分数。移动正确的答案还是可以的。
  • 这段代码一点都不好。 1) 对于非 int 枚举,它将失败 2) 不必要的大规模转换 3) EnumExtender.ToList&lt;T&gt;()(EnumType[])Enum.GetValues(typeof(EnumType)) 是多余的,但效率要低得多(来回转换名称)
【解决方案2】:

我做了一些对我有用且非常简单的事情。由于动态转换的使用,可能效率不高。但也许你会喜欢它?

public static T SetFlag<T>(this Enum value, T flag, bool set)
{
    Type underlyingType = Enum.GetUnderlyingType(value.GetType());

    // note: AsInt mean: math integer vs enum (not the c# int type)
    dynamic valueAsInt = Convert.ChangeType(value, underlyingType);
    dynamic flagAsInt = Convert.ChangeType(flag, underlyingType);
    if (set)
    {
        valueAsInt |= flagAsInt;
    }
    else
    {
        valueAsInt &= ~flagAsInt;
    }

    return (T)valueAsInt;
}

【讨论】:

    【解决方案3】:

    我不确定你的问题是什么,但如果你问这是否可能,我不得不说它不是,不是用这个确切的语法。

    枚举是值类型,因此是按值传递的。因此,接收枚举值的方法(例如 SetFlag)将接收它的副本。即使它设置了一个标志,该更改也将仅限于方法范围,而不是调用它的枚举。

    您可以将它传递给带有 ref 修饰符的方法,如下所示:SetFlag(ref enumVariable, EnumType.SingleFlag) 但据我所知,这不支持作为扩展方法。

    您可以做的是创建一个通用枚举帮助器类:

    public static class EnumHelper
    {
        public void SetFlag<TEnum>(ref TEnum enumValue, TEnum flag)
        {
             enumValue = enumValue | flag;
        }
    }
    

    或者,或者,创建一个返回新值而不是修改现有变量的 SetFlag 方法。

    public static TEnum SetFlag<TEnum>(this TEnum enumValue, TEnum flag)
    {
        return enumValue | flag;
    }
    

    【讨论】:

    • 呸,你是对的。遗憾的是,您不能将 Enum 用作泛型类型约束。
    • 您可以将约束设置为 struct,这可能会有所帮助。
    • 无济于事,因为我仍然无法使用 |操作员。
    【解决方案4】:

    也许没有你希望的那么漂亮,但你可以很简单地做到这一点:)

    enumVariable |= EnumType.SingleFlag;
    

    【讨论】:

      【解决方案5】:

      您可能需要为每个枚举实现该方法,因为您不能以这种方式约束枚举:

      public static T SetFlag<T>(this T @this, T flag, Boolean state) where T : enum { ... }
      

      C# 中不允许对泛型类型进行运算符重载,因此不能在不强制转换的情况下使用泛型类型 T。

      解决方案

      所以你的扩展方法必须是这样的:

      public static MyFlag SetFlag(this MyFlag @this, MyFlag flag, Boolean state) 
      {
          return state ? (@this | flag) : (@this & ~flag);
      }
      

      【讨论】:

      • 谢谢,这是我暂时做的。我想我应该坚持下去。
      • (@this ^ flag) 将设置枚举,如果未设置这不是它在 state == false 上应该做的。 (@this &amp; ~flag)是清旗方式
      • @Firo 你是对的,在我个人的实现中它是正确的。
      猜你喜欢
      • 2011-08-13
      • 1970-01-01
      • 2010-09-21
      • 1970-01-01
      • 2015-10-19
      • 1970-01-01
      • 2013-08-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多