Подтвердить что ты не робот

С#: Enum.IsDefined на комбинированных флагах

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

[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))
4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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, и это должно решить вашу проблему.

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

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

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

И в случае, если вы находитесь на С# 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;
}

Примечание. Это необходимо сделать так:

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