当前位置: 动力学知识库 > 问答 > 编程问答 >

C#: Enum.IsDefined on combined flags

问题描述:

I have this enum:

[Flags]

public enum ExportFormat

{

None = 0,

Csv = 1,

Tsv = 2,

Excel = 4,

All = Excel | Csv | Tsv

}

I am trying to make a wrapper on this (or any, really) enum which notifies on change. Currently it looks like this:

public class NotifyingEnum<T> : INotifyPropertyChanged

where T : struct

{

private T value;

public event PropertyChangedEventHandler PropertyChanged;

public NotifyingEnum()

{

if (!typeof (T).IsEnum)

throw new ArgumentException("Type T must be an Enum");

}

public T Value

{

get { return value; }

set

{

if (!Enum.IsDefined(typeof (T), value))

throw new ArgumentOutOfRangeException("value", value, "Value not defined in enum, " + typeof (T).Name);

if (!this.value.Equals(value))

{

this.value = value;

PropertyChangedEventHandler handler = PropertyChanged;

if (handler != null)

handler(this, new PropertyChangedEventArgs("Value"));

}

}

}

}

Since an enum can be assigned with any value really, I want to check if the given Value is defined. But I found a problem. If I here give it an enum consisting of for example Csv | Excel, then Enum.IsDefined will return false. Apparently because I haven't defined any enum consisting of those two. I guess that on some level is logical, but how should I then check if the given value is valid? In other words, to make it work, what do I need to swap this following line with?

if (!Enum.IsDefined(typeof (T), value))

网友答案:

With flag-based enums, it's about having a bit set or not. So for 'ExportFormat', if bit 1 is set, it's CSV format, even though there might be more bits set. Is having bit 1 and 2 set an invalid value? This is subjective: from the point of view of the values as a group, it is invalid (there's no bitpattern defined for bits 1 and 2 set) however, as each value is a bit, looking at them individually, it can be that a value with bits 1 and 2 set is valid.

If one passes in the value 0011111011, is that a valid value? Well, it depends on what you're looking for: if you are looking at the whole value, then it's an invalid value, but if you're looking at individual bits, it's an ok value: it has bits set which aren't defined, but that's ok, as flag-based enums are checked 'per bit': you're not comparing them to a value, you're checking whether a bit is set or not.

So, as your logic will check on which bits are set to select which formats to pick, it's realy not necessary to check whether the enum value is defined: you have 3 formats: if the bit of the corresponding format is set, the format is selected. That's the logic you should write.

网友答案:

We know that an enum value converted to a string will never start with a digit, but one that has an invalid value always will. Here's the simplest solution:

public static Enum IsDefinedEx(this Enum yourEnum)
{
    char firstDigit = yourEnum.ToString()[0];
    if (Char.IsDigit(firstDigit) || firstDigit == '-')  //Account for signed enums too
        return false;

    return true;
}

Use that extension method instead of the stock IsDefined and that should solve your issue.

网友答案:

I would operate on the bit level and check if all bits set in the new value are set in your All value:

if ( ! (All & NewValue) == NewValue )

You will have to see yourself how you best do that, maybe you need to cast all values to an int and then do the bitwise comparison.

网友答案:

maybe try catch with parse?
wich values you dont want to pass?

    public T Value
    {
        get { return value; }
        set
        {
            try
            {
                Enum.Parse(typeof(T), value.ToString());
            }
            catch 
            {
                throw new ArgumentOutOfRangeException("value", value, "Value not defined in enum, " + typeof(T).Name);
            }
            if (!this.value.Equals(value))
            {
                this.value = value;

                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                    handler(this, new PropertyChangedEventArgs("Value"));
            }
        }
    }
网友答案:

I know this thread hasn't been answered in quite a while, but I figured answering it using a built-in function is good for those that visit this after me.

Using the OP's original enumeration, you can parse a bitmasked value using the following code.

    ExportFormat format;
    if (!Enum.TryParse<ExportFormat>(value.ToString(), out format))
    {
      // Could not parse
    }

Hope that helps.

网友答案:

See here. Quite a lot of code.

网友答案:

Here is a tiny extension method that does it efficiently.

static void Main(string[] args)
{
  var x = ExportFormat.Csv | ExportFormat.Excel;
  var y = ExportFormat.Csv | ExportFormat.Word;
  var z = (ExportFormat)16; //undefined value

  var xx = x.IsDefined();  //true
  var yy = y.IsDefined();  //false
  var zz = z.IsDefined();  //false
}

public static bool IsDefined(this Enum value)
{
  if (value == null) return false;
  foreach (Enum item in Enum.GetValues(value.GetType()))
    if (item.HasFlag(value)) return true;
  return false;
}

[Flags]
public enum ExportFormat                                      
{
  None = 0,
  Csv = 1,
  Tsv = 2,
  Excel = 4,
  Word = 8,
  All = Excel | Csv | Tsv
}

The following approach will work for items combined by code, that is not grouped in the enum:

static void Main(string[] args)
{
  var x = ExportFormat.Csv | ExportFormat.Excel;
  var y = ExportFormat.Csv | ExportFormat.Word;
  var z = (ExportFormat)16; //undefined value

  var xx = x.IsDefined();  //true
  var yy = y.IsDefined();  //true
  var zz = z.IsDefined();  //false
}

public static bool IsDefined(this ExportFormat value)
{
  var max = Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>()
    .Aggregate((e1,e2) =>  e1 | e2);
  return (max & value) == value;
}

And in case you're in C# 4.0 where DLR is supported, you can use the following cool agnostic extension method:

public static bool IsDefined(this Enum value)
{
  dynamic dyn = value;
  var max = Enum.GetValues(value.GetType()).Cast<dynamic>().
    Aggregate((e1,e2) =>  e1 | e2);
  return (max & dyn) == dyn;
}

Note - It must be done this way since:

  1. Operators | and & cannot be applied to operands of type Enum and Enum
  2. These operators are defined in the compiler and are not reflected, so there is no way to retrieve them with reflection / Linq Expressions, trust me - I've tried it all...
分享给朋友:
您可能感兴趣的文章:
随机阅读: