C #: Enum.IsDefined на комбинированные флаги - PullRequest
14 голосов
/ 09 февраля 2009

У меня есть это перечисление:

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

Я пытаюсь сделать обертку для этого (или любого, действительно) перечисления, которое уведомляет об изменениях. В настоящее время это выглядит так:

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

Поскольку перечисление действительно может быть назначено с любым значением, я хочу проверить, определено ли данное Значение. Но я нашел проблему. Если я приведу здесь перечисление, состоящее, например, из Csv | Excel, то Enum.IsDefined вернет false. Очевидно, потому что я не определил ни одного перечисления, состоящего из этих двух. Я предполагаю, что на каком-то уровне это логично, но как мне тогда проверить, является ли данное значение допустимым? Другими словами, чтобы это работало, что мне нужно поменять эту следующую строку?

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

Ответы [ 9 ]

9 голосов
/ 12 января 2012

Мы знаем, что значение перечисления, преобразованное в строку, никогда не начинается с цифры, но всегда будет иметь недопустимое значение. Вот самое простое решение:

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

Используйте этот метод расширения вместо стандартного IsDefined, и это должно решить вашу проблему.

9 голосов
/ 09 февраля 2009

При использовании перечислений на основе флага речь идет о том, установлен бит или нет. Поэтому для «ExportFormat», если установлен бит 1, это формат CSV, хотя может быть установлено больше битов. Биты 1 и 2 устанавливают недопустимое значение? Это субъективно: с точки зрения значений как группы, оно недопустимо (однако для битов 1 и 2 не определен битовый шаблон), так как каждое значение является битом, если рассматривать их по отдельности, то может быть значение с установленными битами 1 и 2 является действительным.

Если передать значение 0011111011, это допустимое значение? Ну, это зависит от того, что вы ищете: если вы смотрите на все значение, то это недопустимое значение, но если вы смотрите на отдельные биты, это нормальное значение: у него установлены биты, которые не являются определены, но это нормально, так как перечисления на основе флагов проверяются «на бит»: вы не сравниваете их со значением, вы проверяете, установлен бит или нет.

Итак, поскольку ваша логика будет проверять, какие биты установлены для выбора, какие форматы выбрать, на самом деле нет необходимости проверять, определено ли значение перечисления: у вас есть 3 формата: если бит соответствующего формата установлен, формат выбран. Это логика, которую вы должны написать.

5 голосов
/ 09 февраля 2009

Я бы работал на уровне битов и проверил, установлены ли все биты, установленные в новом значении, в ваше значение All:

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

Вы должны будете сами увидеть, как вам лучше всего это сделать, возможно, вам нужно привести все значения к int, а затем выполнить побитовое сравнение.

2 голосов
/ 09 февраля 2009

может, попробуйте поймать с разбором?
какие значения вы не хотите передавать?

    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"));
            }
        }
    }
1 голос
/ 19 марта 2018

Вот способ сделать это (использует 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;
    }
1 голос
/ 12 января 2012

Здесь - метод крошечного расширения, который делает это эффективно.

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
}

Следующий подход будет работать для элементов, объединенных кодом, которые не сгруппированы в перечислении:

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

И в случае, если вы находитесь в C # 4.0, где поддерживается DLR, вы можете использовать следующий метод расширения агностического расширения:

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

Примечание - это должно быть сделано так:

  1. Операторы | и & нельзя применять к операндам типа Enum и Enum
  2. Эти операторы определены в компиляторе и не отражены, поэтому нет способа получить их с помощью отражений / выражений Linq, поверьте мне - я пробовал все это ...
1 голос
/ 11 августа 2011

Я знаю, что на эту ветку давно не отвечали, но я подумал, что отвечать на нее с помощью встроенной функции хорошо для тех, кто посещает эту тему после меня.

Используя исходное перечисление OP, вы можете проанализировать значение битовой маски, используя следующий код.

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

Надеюсь, это поможет.

0 голосов
/ 03 декабря 2018
[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
0 голосов
/ 09 февраля 2009

См. здесь . Довольно много кода.

...