tags:

views:

2216

answers:

9

What I want to do is something like this: I have enums with combined flagged values.

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo ) 
        where T:enum //the constraint I want that doesn't exist in C#3
    {    
     return (input & matchTo) != 0;
    }
}

So then I could do:

MyEnum tester = MyEnum.FlagA | MyEnum.FlagB

if( tester.IsSet( MyEnum.FlagA ) )
    //act on flag a

Unfortunately C#'s generic where constraints have no enum restriction, only class and struct. C# doesn't see enums as structs (even though they are value types) so I can't add extension types like this.

Anyone know a workaround?

+2  A: 

try this

public static class EnumExtension
{    
    public static bool IsSet( this Enum input, Enum matchTo )   
    {            
        return (input & matchTo) != 0;    
    }
}

if i remember right, that works. doesn't let you enforce the same enumeration though, but you could do that easily enough in the logic and throw an exception if they aren't the same type.

Darren Kopp
Blixt
+12  A: 

Darren, that would work if the types were specific enumerations - for general enumerations to work you have to cast them to ints (or more likely uint) to do the boolean math:

public static bool IsSet( this Enum input, Enum matchTo )
{
    return ( Convert.ToUInt32( input ) & Convert.ToUInt32( matchTo ) ) != 0;
}
Ronnie
And if you have a ridiculous number of flags, you can call GetTypeCode() on the arguments and Convert.ToUint64()
Kit
+1  A: 

Using your original code, inside the method you can also use reflection to test that T is an enum:

public static class EnumExtension
{
    public static bool IsSet<T>( this T input, T matchTo )
    {
        if (!typeof(T).IsEnum)
        {
            throw new ArgumentException("Must be an enum", "input");
        }
        return (input & matchTo) != 0;
    }
}
Scott Dorman
Thanks, but that turns a compile time issue (the where constraint) into a runtime one (your exception).Also you'd still need to convert the inputs to ints before you could do anything with them.
Keith
+2  A: 

The way I do it is put a struct constraint, then check that T is an enum at runtime. This doesn't eliminate the problem completely, but it does reduce it somewhat

thecoop
where T : struct, IComparable, IFormattable, IConvertible -- this is the closest you can get to enum :)
Kit
+1  A: 

Jon Skeet actually blogged about this yesterday:

http://msmvps.com/blogs/jon_skeet/archive/2009/09/10/generic-constraints-for-enums-and-delegates.aspx

Patrik
I know - if he answers this I'll pick his answer.
Keith
+13  A: 

EDIT: This is now live in version 0.0.0.2 of UnconstrainedMelody.

(As requested on my blog post about enum constraints. I've included the basic facts below for the sake of a standalone answer.)

The best solution is to wait for me to include it in UnconstrainedMelody1. This is a library which takes C# code with "fake" constraints such as

where T : struct, IEnumConstraint

and turns it into

where T : struct, System.Enum

via a postbuild step.

It shouldn't be too hard to write IsSet... although catering for both Int64-based and UInt64-based flags could be the tricky part. (I smell some helper methods coming on, basically allowing me to treat any flags enum as if it had a base type of UInt64.)

What would you want the behaviour to be if you called

tester.IsSet(MyFlags.A | MyFlags.C)

? Should it check that all the specified flags are set? That would be my expectation.

I'll try to do this on the way home tonight... I'm hoping to have a quick blitz on useful enum methods to get the library up to a usable standard quickly, then relax a bit.

EDIT: I'm not sure about IsSet as a name, by the way. Options:

  • Includes
  • Contains
  • HasFlag (or HasFlags)
  • IsSet (it's certainly an option)

Thoughts welcome. I'm sure it'll be a while before anything's set in stone anyway...


1 or submit it as a patch, of course...

Jon Skeet
I suppose if multiple flags are passed in it should check for all of them. My actual fix for this (back in 2008 when I asked it) was to have a template extension method for each flags enum - messy but works. Never bothered with the check for multiple flags because all the checks we have are for a single flag - not such a problem in internal only code but something that would need to be accounted for in an shared library.
Keith
You had to go and mention PostSharp LOL :o http://www.postsharp.org/blog/generic-constraints-for-enums-and-delegates
280Z28
I would use the Flags-terminology simply because it already exists in .NET (see `FlagsAttribute`.) I see two explicit names here: `HasAnyFlag` and `HasAllFlags`. They can be shortened to `HasFlag` and `HasFlags`. I can't say which is best, it's a matter of taste I guess.
Blixt
Oh and Keith, have a look at http://stackoverflow.com/questions/1404077/is-there-a-workaround-for-generic-type-constraint-of-special-class-enum-in-c-3 `=)`
Blixt
Or actually simpler HasAny() and HasAll()
Keith
Yes, I agree that's even better. `colors.HasAny(Colors.Red | Colors.Blue)` looks like very readable code. `=)`
Blixt
Yup, I like HasAny and HasAll too. Will go with that.
Jon Skeet
HasAny and HasAll seem awesome.
IDisposable
A: 

Actually, it is possible, with an ugly trick. However, it cannot be used for extension methods.

public abstract class Enums<Temp> where Temp : class {
    public static TEnum Parse<TEnum>(string name) where TEnum : struct, Temp {
     return (TEnum)Enum.Parse(typeof(TEnum), name); 
    }
}
public abstract class Enums : Enums<Enum> { }

Enums.IsSet<DateTimeKind>("Local")

If you want to, you can give Enums<Temp> a private constructor and a public nested abstract inherited class with Temp as Enum, to prevent inherited versions for non-enums.

SLaks
A: 

Here's some code that I just did up that seems to work like you want without having to do anything too crazy. It's not restricted to only enums set as Flags, but there could always be a check put in if need be.

public static class EnumExtensions
{
    public static bool ContainsFlag(this Enum source, Enum flag)
    {
        var sourceValue = ToUInt64(source);
        var flagValue = ToUInt64(flag);

        return (sourceValue & flagValue) == flagValue;
    }

    public static bool ContainsAnyFlag(this Enum source, params Enum[] flags)
    {
        var sourceValue = ToUInt64(source);

        foreach (var flag in flags)
        {
            var flagValue = ToUInt64(flag);

            if ((sourceValue & flagValue) == flagValue)
            {
                return true;
            }
        }

        return false;
    }

    // found in the Enum class as an internal method
    private static ulong ToUInt64(object value)
    {
        switch (Convert.GetTypeCode(value))
        {
            case TypeCode.SByte:
            case TypeCode.Int16:
            case TypeCode.Int32:
            case TypeCode.Int64:
                return (ulong)Convert.ToInt64(value, CultureInfo.InvariantCulture);

            case TypeCode.Byte:
            case TypeCode.UInt16:
            case TypeCode.UInt32:
            case TypeCode.UInt64:
                return Convert.ToUInt64(value, CultureInfo.InvariantCulture);
        }

        throw new InvalidOperationException("Unknown enum type.");
    }
}
Brian Surowiec
A: 

This doesn't answer the original question, but there is now a method in .NET 4 called Enum.HasFlag which does what you are trying to do in your example

Phil Devaney