是否可能创建一个通用的按位枚举“IsOptionSet()”方法?

4
下面的代码使得传入一组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 属性的枚举一起使用?


你可以切换到 Delphi - 这个功能是内置的 :) - Ray
早些年我曾经使用Delphi :) 有趣的是,Delphi的创造者是微软聘请来创建C#的人 - 所以很遗憾他放弃了这个功能。 - Peter Bridger
3个回答

7

编辑:

最简单、最好的方法是升级到VS2010 Beta2,并使用.NET 4的Enum.HasFlag 方法。框架团队添加了许多有用的功能,使枚举更易用。


原文(当前.NET版本):

您可以通过传递枚举而不是泛型来实现此目的:

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

不过,这种方法的缺点是无法防止将两种不同枚举类型传递给例程。因此,使用时需要合理。


它也经常出现盒子效应。通过足够的工作,你可以使它更高效和愉悦 :) - Jon Skeet
1
是的,没错。虽然它确实有效,并且可以处理奇怪的情况。好在这在.NET 4中得到了修复(Enum.HasFlag):http://msdn.microsoft.com/en-us/library/system.enum.hasflag(VS.100).aspx - Reed Copsey
制作一个既简单又能够实现这一点的东西需要相当多的工作。 - Reed Copsey
.NET 4增强的消息很有趣 - 总有一天我们只需要编写纯应用程序代码! :) - Peter Bridger

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

编辑:正如评论中指出的那样,如果枚举不是int类型(这是枚举的默认类型),则此方法将无法工作。它应该被命名为其他名称以表示这一点,但对于大多数情况来说,它可能已经足够好了,除非您需要一个具有超过31个值的标志集。


哈哈,通过将对象转换为Object类型,解决了我在反射中需要解决的很多问题。 - sisve
1
System.Convert.ToInt32(value) 比强制转换更加简洁。 - Sam Harwell
这个并不能处理使用无符号或长存储类型的枚举,也不能保护你免于不幸地将其他非枚举类型传递给它。比如,(3.14).IsOptionSet(3) 将会在智能感知中显示。:( - Reed Copsey
实际上,情况更糟。由于对象转换,每当枚举不是Int32时,都会引发InvalidCastException异常。您无法解除装箱并将其转换为不同类型而不出现异常。尝试使用定义为“public enum HtmlParserOptions:ulong {...}”的枚举运行此操作。 - Reed Copsey

3

嗯,有点道理。

你不能添加约束来确保类型参数是“标志”枚举,在普通的C#中,你也不能添加约束来确保它首先是一个枚举...但是通过一些技巧,你可以让后者工作。这是IL中的有效约束,但不是C#中的有效约束。然后,您需要做一些工作以使“and”部分在通用情况下工作。

我有一个名为Unconstrained Melody的项目,其中包含一些有用的枚举扩展方法,通过一些IL重写实现。在这种情况下,您将使用:

if (options.HasAny(optionToTest))

或者

if (options.HasAll(optionToTest))

根据您处理optionToTest实际上是多个组合标志的方式,可能会有所不同。

或者,等待 .NET 4.0 - BCL中的更改包括 Enum.HasFlag, 我认为这将实现您想要的功能。


Jon:在没有使用IL重写的情况下,有没有一种比我的方法更好的纯C#方式来做到这一点(在<.net4中)? - Reed Copsey
@Reed:可能不是。幸运的是,UnconstrainedMelody的使用者不需要关心我是否使用了IL重写——他们只需将其视为任何其他库即可。 - Jon Skeet
当然可以 :) 我只是好奇你是否知道在C#中直接实现这个的方法 - 而不会产生奇怪的副作用。谢谢。 - Reed Copsey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接