7

I can | and &, etc, an enum, but not an Enum. Why is this? Is there any way around this? I'm trying to write an Enum flag converter for WPF.

public class EnumFlagConverter : IValueConverter
{
    public Enum CurrentValue;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var theEnum = value as Enum;
        CurrentValue = theEnum;
        return theEnum.HasFlag(parameter as Enum);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Enum CurrentValue;
        var theEnum = parameter as Enum;
        if (CurrentValue.HasFlag(theEnum)) //this line is allowed
            return CurrentValue &= ~theEnum; //~theEnum not allowed, neither is the &=
        else
            return CurrentValue |= theEnum;  // |= cannot be applied to Enum and Enum
    }
}
DLeh
  • 23,806
  • 16
  • 84
  • 128
  • Could you specify the type as `int` instead, and hope that implicit conversion to `Enum` works? – Matthew Jun 11 '14 at 20:59
  • 2
    Would you accept "Because Enum is a class that doesn't have those operations defined" I guess I'm having trouble understanding the confusion. It's the same reason you can't do string foo |= "bar" – Pete Baughman Jun 11 '14 at 21:14
  • It may also be helpful to understand that "Enum" (System.Enum actually) is a type, whereas "enum" is a keyword that's used to declare a type. – Pete Baughman Jun 11 '14 at 21:25
  • 1
    How are you going to keep track of the "current value" of the binding source when the converter is used for multiple bindings? IMO the back-conversion doesn't make sense at all. You should at least remove the local `CurrentValue` variable from the `ConvertBack` method. – Clemens Jun 11 '14 at 21:33
  • @Clemens I wouldn't have to use the same converter for multiple bindings, if that's a limitation I'd have to deal with. – DLeh Jun 12 '14 at 00:44
  • @Clemens Also i want to use this with a group of checkboxes, so it makes sense to share the same CurrentValue between them all. – DLeh Jun 12 '14 at 00:53
  • a tip: add your code as an answer instead of keeping it on pastebin. If others visit your question and quickly see "this is my problem too!" they'll scroll down to the answers and miss your last comment. – default Jun 12 '14 at 11:09

2 Answers2

10

Why is this?

In cases where the compiler knows the enumeration's underlying type, the compiler can perform bitwise operations without any issues. In cases where the compiler doesn't know the underlying type, it cannot know whether you want an 8-bit, a 16-bit, a 32-bit, or perhaps even a 64-bit operation, and just gives up entirely. Note also that the compiler has no way of knowing that neither of your two enumeration values is null, and that the compiler has no way of knowing that the two enumeration values have the same type or even width.

Is there any way around this?

You can know that you will never be dealing with enumerations larger than 64 bits, and that a 64-bit operation will produce the correct results even for 8-bit enumeration types here. Therefore, you can help the compiler by writing your operations as 64-bit operations explicitly.

static Enum Or(Enum a, Enum b)
{
    // consider adding argument validation here

    if (Enum.GetUnderlyingType(a.GetType()) != typeof(ulong))
        return (Enum)Enum.ToObject(a.GetType(), Convert.ToInt64(a) | Convert.ToInt64(b));
    else
        return (Enum)Enum.ToObject(a.GetType(), Convert.ToUInt64(a) | Convert.ToUInt64(b));
}

similarly for And.

  • Perfect! I knew there was some way to turn an Enum into it's underlying object, but I didn't know about the `ToObject` method. Thanks! – DLeh Jun 12 '14 at 11:00
3

Using the accepted answer, I crafted this converter to bind multiple checkboxes to a [Flags]Enum. Note: this converter uses a class member, so do not reuse the same converter instance for multiple sets of bindings.

XAML:

<StackPanel>
    <StackPanel.Resources>
        <local:EnumFlagConverter x:Key="myConverter" />
    </StackPanel.Resources>
    <CheckBox Content="Option1" IsChecked="{Binding TheEnum, Converter={StaticResource myConverter}, ConverterParameter={x:Static local:MyEnum.Option1}}" />
    <CheckBox Content="Option2" IsChecked="{Binding TheEnum, Converter={StaticResource myConverter}, ConverterParameter={x:Static local:MyEnum.Option2}}" />
    <CheckBox Content="Option3" IsChecked="{Binding TheEnum, Converter={StaticResource myConverter}, ConverterParameter={x:Static local:MyEnum.Option3}}" />
</StackPanel>

C#:

public class EnumFlagConverter : IValueConverter
{
    public Enum CurrentValue { get; set; }
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var theEnum = value as Enum;
        CurrentValue = theEnum;
        return theEnum.HasFlag(parameter as Enum);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var theEnum = parameter as Enum;
        if ((bool)value)
            CurrentValue = CurrentValue.Or(theEnum);
        else
            CurrentValue = CurrentValue.And(theEnum.Not());
        return CurrentValue;
    }
}


public static class Extensions
{
    public static Enum Or(this Enum a, Enum b)
    {
        // consider adding argument validation here
        if (Enum.GetUnderlyingType(a.GetType()) != typeof(ulong))
            return (Enum)Enum.ToObject(a.GetType(), Convert.ToInt64(a) | Convert.ToInt64(b));
        else
            return (Enum)Enum.ToObject(a.GetType(), Convert.ToUInt64(a) | Convert.ToUInt64(b));
    }

    public static Enum And(this Enum a, Enum b)
    {
        // consider adding argument validation here
        if (Enum.GetUnderlyingType(a.GetType()) != typeof(ulong))
            return (Enum)Enum.ToObject(a.GetType(), Convert.ToInt64(a) & Convert.ToInt64(b));
        else
            return (Enum)Enum.ToObject(a.GetType(), Convert.ToUInt64(a) & Convert.ToUInt64(b));
    }
    public static Enum Not(this Enum a)
    {
        // consider adding argument validation here
        return (Enum)Enum.ToObject(a.GetType(), ~Convert.ToInt64(a));
    }
}
Chris Schaller
  • 13,704
  • 3
  • 43
  • 81
DLeh
  • 23,806
  • 16
  • 84
  • 128