【问题标题】:Is it possible to create a generic bitwise enumeration 'IsOptionSet()' method?是否可以创建一个通用的按位枚举“IsOptionSet()”方法?
【发布时间】:2009-10-23 15:58:42
【问题描述】:

下面的代码可以很容易地传入一组 HtmlParserOptions,然后检查单个选项以查看它是否被选中。

[Flags]
public enum HtmlParserOptions
{
    NotifyOpeningTags = 1,
    NotifyClosingTags = 2,
    NotifyText = 4,
    NotifyEmptyText = 8
}

private bool IsOptionSet(HtmlParserOptions options, HtmlParserOptions singleOption)
{
    return (options & singleOption) == singleOption;
}

我的问题是,是否可以创建一个通用版本(我猜是通过在方法属性上实现一个接口),它可以与带有 Flags 属性的 any 枚举一起使用?

【问题讨论】:

  • 你可以切换到 Delphi - 这个功能是内置的 :)
  • 回到我曾经使用 Delphi 的那一天 :) 有趣的是,Delphi 的创建者是微软带来创建 C# 的人 - 所以很遗憾他放弃了这个功能

标签: c# .net enums bit-manipulation


【解决方案1】:

编辑:

最简单、最好的选择是升级到 VS2010 Beta2 并使用 .NET 4 的 Enum.HasFlag 方法。框架团队为 Enum 添加了许多不错的附加功能,以使它们更好用。


原件(适用于当前的 .NET):

您可以通过传递 Enum 而不是泛型来做到这一点:

static class EnumExtensions
{
    private static bool IsSignedTypeCode(TypeCode code)
    {
        switch (code)
        {
            case TypeCode.Byte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
                return false;
            default:
                return true;
        }
    }

    public static bool IsOptionSet(this Enum value, Enum option)
    {
        if (IsSignedTypeCode(value.GetTypeCode()))
        {
            long longVal = Convert.ToInt64(value);
            long longOpt = Convert.ToInt64(option);
            return (longVal & longOpt) == longOpt;
        }
        else
        {
            ulong longVal = Convert.ToUInt64(value);
            ulong longOpt = Convert.ToUInt64(option);
            return (longVal & longOpt) == longOpt;
        }
    }
}

这很完美,就像这样:

class Program
{
    static void Main(string[] args)
    {
        HtmlParserOptions opt1 = HtmlParserOptions.NotifyText | HtmlParserOptions.NotifyEmptyText;
        Console.WriteLine("Text: {0}", opt1.IsOptionSet(HtmlParserOptions.NotifyText));
        Console.WriteLine("OpeningTags: {0}", opt1.IsOptionSet(HtmlParserOptions.NotifyOpeningTags));

        Console.ReadKey();
    } 
}

以上打印:

Text: True
OpeningTags: False

不过,这样做的缺点是它不能保护您免于将两种不同类型的 Enum 类型传递到例程中。一定要合理使用。

【讨论】:

  • 它也有很多盒子。通过足够的工作,您可以使其更加高效和愉快:)
  • 是的,没错。不过,它确实有效,并且可以处理奇怪的情况。好在这已在 .NET 4 (Enum.HasFlag) 中得到修复:msdn.microsoft.com/en-us/library/…
  • 尽管如此,制作简单的东西并做到这一点是相当多的工作。
  • 关于 .net 4 增强的有趣新闻 - 有一天我们只需要编写纯应用程序代码! :)
【解决方案2】:

嗯,有点。

你不能添加一个约束来确保类型参数是一个“标志”枚举,并且在纯 C# 中你不能添加一个约束来确保它首先是一个枚举......但是稍微玩一下,你就可以让后者工作。这是 IL 中的有效约束,但在 C# 中不是。然后,您需要做一些工作才能使“和”部分正常工作。

我有一个名为 Unconstrained Melody 的项目,它通过一些 IL 重写在枚举上提供了一些有用的扩展方法。在这种情况下,您将使用:

if (options.HasAny(optionToTest))

if (options.HasAll(optionToTest))

取决于您想如何处理optionToTest 实际上是多个组合标志的情况。

或者,等待 .NET 4.0 - changes in the BCL 包括 Enum.HasFlag,我认为它可以满足您的需求。

【讨论】:

  • Jon:有没有比我的方法更好的纯 C# 方法(在 <.net4 il>
  • @Reed:可能不会。幸运的是,UnconstrainedMelody 的消费者不需要关心我使用 IL 重写 - 他们只是像对待任何其他库一样对待它。
  • 当然 :) 我只是好奇你是否知道直接在 C# 中执行此操作的方法 - 没有奇怪的副作用。谢谢。
【解决方案3】:
public static bool IsOptionSet<T>(this T flags, T option) where T : struct
{
    if(! flags is int) throw new ArgumentException("Flags must be int");

    int opt = (int)(object)option;
    int fl = (int)(object)flags;
    return (fl & opt) == opt;
}

编辑:正如 cmets 中所指出的,如果枚举不是 int (这是枚举的默认值),这将不起作用。它可能应该被命名为其他名称来表明这一点,但对于大多数情况而言,它可能“足够好”,除非您需要一组具有超过 31 个值的标志。

【讨论】:

  • 哈,通过强制转换到对象解决了很多我必须通过反射解决的问题。
  • System.Convert.ToInt32(value) 比演员表干净得多。
  • 不幸的是,这不处理具有无符号或长存储类型的枚举,或者保护您免于将其他一些非枚举传递给此。 (这将显示在双精度上,例如:(3.14).IsOptionSet(3) 将显示在智能感知中:(
  • 其实更糟。由于对象转换,只要您枚举不是 Int32,就会抛出 InvalidCastException。您不能毫无例外地取消装箱 + 强制转换为不同的类型。尝试使用枚举定义的“public enum HtmlParserOptions : ulong { ...”来运行它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-18
  • 2015-10-19
  • 2011-02-27
  • 1970-01-01
相关资源
最近更新 更多