views:

392

answers:

3

The below code makes it easy to pass in a set HtmlParserOptions and then check against a single option to see if it's been selected.

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

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

My question is, is it possible to create a Generic version of this (I'm guessing through implementing an interface on the method properties) that will work with any enumeration with the Flags attribute?

+1  A: 
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;
}

EDIT: As has been pointed out in the comments, this won't work if the enum is anything other than an int (which is the default for enums). It should be probably be named something else to indicate this, but it's probably 'good enough' for the majority of cases unless you need a set of flags with more than 31 values.

Lee
Hah, going via a cast to object solved alot of the problem I had to solve with reflection.
Simon Svensson
`System.Convert.ToInt32(value)` is much cleaner than the casts.
280Z28
This doesn't handle enums with unsigned or long storage types, or protect you from passing some other, non-enum, to this, unfortunately. (This will show up on a double, for example: (3.14).IsOptionSet(3) will show up in intellisense :(
Reed Copsey
Actually, it's even worse. This throws InvalidCastException any time you're enum isn't an Int32, due to the object cast. You can't unbox + cast to a different type without an exception. Try runniig this with the enum defined "public enum HtmlParserOptions : ulong { ..."
Reed Copsey
+3  A: 

Edit:

The easiest, nicest option is to upgrade to VS2010 Beta2 and use .NET 4's Enum.HasFlag method. The framework team has added a lot of nice additions to Enum to make them nicer to use.


Original (for current .NET):

You can do this by passing Enum, instead of generics:

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

This works perfectly, like so:

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

THe above prints:

Text: True
OpeningTags: False

The downside to this, though, is it doesn't protect you from passing two different types of Enum types into the routine. You have to use it reasonably.

Reed Copsey
It also boxes quite a lot. With enough work, you can make it more efficient and pleasant :)
Jon Skeet
Yeah, true. It does work, though, and handles odd cases. Good thing this is fixed in .NET 4 (Enum.HasFlag) : http://msdn.microsoft.com/en-us/library/system.enum.hasflag(VS.100).aspx
Reed Copsey
It's a fair amount of work to make something that is simple and does this, though.
Reed Copsey
Interesting news on the .net 4 enhancement - one of these days we'll only need to write pure application code! :)
Peter Bridger
+2  A: 

Well, sort of.

You can't add a constraint to make sure that the type argument is a "flags" enum, and in plain C# you can't add a constraint to make sure that it's an enum in the first place... but with a bit of jiggery-pokery you can get the latter to work. It's a valid constraint in IL, but not in C#. You'd then need to do a bit of work to get the "and" part working generically.

I have a project called Unconstrained Melody which has some useful extension methods on enums, via some IL-rewriting. In this case you'd use:

if (options.HasAny(optionToTest))

or

if (options.HasAll(optionToTest))

depending on how you wanted to handle the case where optionToTest is actually multiple combined flags.

Alternatively, wait for .NET 4.0 - the changes in the BCL include Enum.HasFlag, which I think will do what you want.

Jon Skeet
Jon: Is there a pure C# way of doing this better than my approach, though (in <.net4), without using the IL rewriting?
Reed Copsey
@Reed: Possibly not. Fortunately, consumers of UnconstrainedMelody don't need to care that I use IL rewriting - they just treat it like any other library.
Jon Skeet
Of course :) I was just curious if you knew of a way to do this in C# directly - without having odd side effects. Thanks.
Reed Copsey