22

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))
Svish
  • 152,914
  • 173
  • 462
  • 620

11 Answers11

13

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 bool 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.

ims1234
  • 183
  • 1
  • 7
Dan McCann
  • 383
  • 3
  • 11
  • 5
    You might also want to check for the character '-', since enums can be represented by signed values. – sethobrien Apr 25 '12 at 23:52
  • 1
    Brilliant approach! Thank you very much. **PS:** The return type of your method is wrong. You probably thought about returning a boolean? ;) – KnorxThieus Aug 31 '17 at 07:06
  • Note that `yourEnum.ToString()` returns a localized string, so the negative sign will be the value of [`CultureInfo.CurrentCulture.NumberFormat.NegativeSign`](https://learn.microsoft.com/en-us/dotnet/api/system.globalization.numberformatinfo.negativesign) which is not necessarily `'-'`. See https://dotnetfiddle.net/UM74C1 for a demo of all negative signs in use by known system-defined cultures. Thus you really need to check whether the `ToString()` value starts with `NumberFormatInfo.CurrentInfo.NegativeSign` – dbc May 09 '22 at 14:17
  • But even checking for the `ToString()` value starting with `CultureInfo.CurrentCulture.NumberFormat.NegativeSign` isn't guaranteed to be foolproof, because it's possible to define custom cultures with an alphabetic number sign, e.g. `NumberFormatInfo.CurrentInfo.NegativeSign = "Neg"`. – dbc May 09 '22 at 14:20
8

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.

Frans Bouma
  • 8,259
  • 1
  • 27
  • 28
  • And how would you write that logic? – Svish Feb 09 '09 at 09:34
  • The logic I was referring to is the logic which tests if a bit is set. So in your case, you have 3 bit tests, one for each bit defined in the enum, and act accordinly, e.g. 'Csv' is set, so export using Csv. – Frans Bouma Feb 09 '09 at 11:34
  • 1
    -> it has bits set which aren't defined, but that's ok. I think that this is wrong and invalidates part of the answer. Having bits set that have no meaning means that something is wrong somewhere so you should throw in this situation. – Ignacio Soler Garcia Dec 30 '14 at 16:11
6

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.

Treb
  • 19,903
  • 7
  • 54
  • 87
  • That could kind of work, except I can not use & nor == on operands of type T. So would have to start converting and casting and stuff, and would like to prevent that... – Svish Feb 09 '09 at 09:38
  • This actually answers the question in a reasonable and useful way. It will not find invalid combinations, but it can weed out invalid set bits nice and simple. – Wilbert Dec 04 '15 at 10:23
4
[Flags] enum E { None = 0, A = '1', B = '2', C = '4' }

public static bool IsDefined<T>(T value) where T : Enum
{
    var values = Enum.GetValues(typeof(T)).OfType<dynamic>().Aggregate((e1, e2) => (e1 | e2));

    return (values & value) == value;
}

// IsDefined(ExportFormat.Csv); // => True
// IsDefined(ExportFormat.All); // => True
// IsDefined(ExportFormat.All | ExportFormat.None); // => True
// IsDefined(ExportFormat.All | ExportFormat.Csv); // => True
// IsDefined((ExportFormat)16); // => False
// IsDefined((ExportFormat)int.MaxValue); // => False

// IsDefined(E.A); // => True
// IsDefined(E.A | E.B); // => True
// IsDefined((E)('1' | '2')); // => True
// IsDefined((E)('5')); // => True
// IsDefined((E)5); // => True
// IsDefined((E)8); // => False
// IsDefined((E)int.MaxValue); // => False
Andriy Tolstoy
  • 5,690
  • 2
  • 31
  • 30
  • Operator '|' cannot be applied to operands of type 'System.Enum' and 'System.Enum' – vusaldev Aug 14 '23 at 11:14
  • According to [Enum values and operations](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/enums#196-enum-values-and-operations) you can use it. – Andriy Tolstoy Aug 25 '23 at 14:08
4

Take a look at the IsValid method of the Enums.NET library:

var validExportFormat = ExportFormat.Excel | ExportFormat.Csv;
validExportFormat.IsValid(EnumValidation.IsValidFlagCombination); // => true

var invalidExportFormat = (ExportFormat)100;
invalidExportFormat.IsValid(EnumValidation.IsValidFlagCombination); // => false
Andriy Tolstoy
  • 5,690
  • 2
  • 31
  • 30
2

Any valid combination of enum values produces a non-numeric value:

public static class EnumExtensions
{
    public static bool IsDefined(this Enum value) => !ulong.TryParse(value.ToString(), out _);
}
Andriy Tolstoy
  • 5,690
  • 2
  • 31
  • 30
  • Can you explain "Any valid combination of enum values produces a non-numeric value" with examples. – M.Hassan Mar 16 '20 at 16:17
  • @M.Hassan the method ```ToString``` will not match a value that is not defined in the enumeration with the name of the constant. Only values that are defined in the enumeration will be matched with the names of the constants. See examples here - https://learn.microsoft.com/en-us/dotnet/api/system.flagsattribute?view=netframework-4.8#examples – Andriy Tolstoy Mar 17 '20 at 16:10
  • Thanks for clarification and the example reference. Just a suggestion, add this comment to your answer. – M.Hassan Mar 18 '20 at 02:42
2

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...
Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
1

Here's a way to do it (uses Linq):

    private static bool IsDefined<T>(long value) where T : struct
    {
        var max = Enum.GetValues(typeof(T)).Cast<T>()
            .Select(v => Convert.ToInt64(v)).
            Aggregate((e1, e2) => e1 | e2);
        return (max & value) == value;
    }
Iain Ballard
  • 4,433
  • 34
  • 39
1

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"));
            }
        }
    }
Avram
  • 4,267
  • 33
  • 40
1

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.

  • 2
    Not working for an invalid value, for e.g.: ```var value = (ExportFormat)50;```. If value is the string representation of an integer that does not represent an underlying value of the enumeration, the method ```Enum.TryParse``` returns an enumeration member whose underlying value is value converted to an integral type. – Andriy Tolstoy Dec 03 '19 at 13:03
0

See here. Quite a lot of code.

Anton Gogolev
  • 113,561
  • 39
  • 200
  • 288