Объявление побитовых перечислений (флагов) - недавняя причуда синтаксиса c # для стиля DWORD?

Предпосылки: Побитовые перечисления полезны для «более удобочитаемого» сравнения и проверки: т.е. OpenFile(write | append).

Я видел несколько способов объявления побитовых перечислений в C #, но в последнее время один из распространенных шаблонов, похоже, больше не возвращает уникальные значения, и мне было интересно, объявляю ли я это неправильно или что-то изменилось. Я говорю о стиле «DWORD» (шестнадцатеричный?) (Показан ниже), который при перечислении в VS2012 RC дает значения как 1, 2, 3, 4 ... вместо ожидаемого побитового удвоения.

Может ли кто-нибудь еще воспроизвести это? Я отправляю код, который использовал для проверки, вместе с выводом на консоль; странное поведение происходит с ComparisonsDword, как вы можете видеть по выходным данным для «Перечисление флагов, явные значения с DWORD».

Без флагов, обычное перечисление

/// <summary>
/// How to compare filter values; no underlying type declared, not flag
/// </summary>
public enum ComparisonsNotInt {
    [Description("x")]
    None
    ,
    [Description("!=")]
    NotEqual
    ,
    [Description("=")]
    Equal
    ,
    [Description(">")]
    GreaterThan
    ,
    [Description("<")]
    LessThan
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsNotFlag

Без флагов, базовый тип = int

/// <summary>
/// How to compare filter values, not flag but underlying type declared
/// </summary>
public enum ComparisonsNotFlag : int {
    [Description("x")]
    None
    ,
    [Description("!=")]
    NotEqual
    ,
    [Description("=")]
    Equal
    ,
    [Description(">")]
    GreaterThan
    ,
    [Description("<")]
    LessThan
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsNotFlag

Флаг, неявное значение

/// <summary>
/// How to compare filter values; values default to whatever .NET decides
/// </summary>
[Flags]
public enum ComparisonsImplicit : int {
    [Description("x")]
    None
    ,
    [Description("!=")]
    NotEqual
    ,
    [Description("=")]
    Equal
    ,
    [Description(">")]
    GreaterThan
    ,
    [Description("<")]
    LessThan
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsImplicit

Флаг, явное значение

/// <summary>
/// How to compare filter values; values explicitly defined with doubled numbers
/// </summary>
[Flags]
public enum ComparisonsExplicit : int {
    [Description("x")]
    None = 0
    ,
    [Description("!=")]
    NotEqual = 1
    ,
    [Description("=")]
    Equal = 2
    ,
    [Description(">")]
    GreaterThan = 4
    ,
    [Description("<")]
    LessThan = 8
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsExplicit

Флаг, явное значение с использованием стиля DWORD Примечание: это то, что неправильно предоставляет уникальные значения, поэтому такие комбинации, как GreaterThanOrEqual, не работают.

/// <summary>
/// How to compare filter values; values explicitly defined with DWORD style
/// </summary>
[Flags]
public enum ComparisonsDword : int {
    [Description("x")]
    None = 0x0
    ,
    [Description("!=")]
    NotEqual = 0x1
    ,
    [Description("=")]
    Equal = 0x2
    ,
    [Description(">")]
    GreaterThan = 0x3
    ,
    [Description("<")]
    LessThan = 0x4
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsDword

Флаг, явное значение с использованием стиля DWORD Примечание: также недопустимые значения, просто проверяется, влияет ли базовый тип на проблему.

/// <summary>
/// How to compare filter values; values explicitly defined with DWORD style
/// </summary>
[Flags]
public enum ComparisonsDwordNotInt {
    [Description("x")]
    None = 0x0
    ,
    [Description("!=")]
    NotEqual = 0x1
    ,
    [Description("=")]
    Equal = 0x2
    ,
    [Description(">")]
    GreaterThan = 0x3
    ,
    [Description("<")]
    LessThan = 0x4
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsDword

Флаг, явное значение с использованием стиля сдвига битов

/// <summary>
/// How to compare filter values; values explicitly set using shorthand of bitwise shifting
/// </summary>
[Flags]
public enum ComparisonsBitshift : int {
    [Description("x")]
    None = 0
    ,
    [Description("!=")]
    NotEqual = 1 << 0
    ,
    [Description("=")]
    Equal = 1 << 1
    ,
    [Description(">")]
    GreaterThan = 1 << 2
    ,
    [Description("<")]
    LessThan = 1 << 3
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
    /// </summary>
    [Description("<=")]
    LessThanOrEqual = (Equal | LessThan)
    ,
    /// <summary>
    /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
    /// </summary>
    [Description(">=")]
    GreaterThanOrEqual = (Equal | GreaterThan)
}//--   enum    ComparisonsBitshift

Вывод из перечисления:

    Plain enum ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 3
    Enum = GreaterThan      ,        Descr = >,     Value = 3   // bad: should be GTE
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Plain enum, underlying int ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 3
    Enum = GreaterThan      ,        Descr = >,     Value = 3   // bad: should be GTE
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Flag enum, implicit values ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3   // bad: should be GT
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Flag enum, explicit values ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 4
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 6
    Enum = LessThan         ,        Descr = <,     Value = 8
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 10

    Flag enum, explicit values with DWORD ----                  // all of these are weirdly unexpected
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Flag enum, explicit values with DWORD, not underlying int ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 3
    Enum = LessThan         ,        Descr = <,     Value = 4
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 6

    Flag enum, explicit values with bitshifting ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 4
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 6
    Enum = LessThan         ,        Descr = <,     Value = 8
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 10

Ссылки:

  1. Что означает атрибут Enum [Flags] в C #?
  2. Типы перечисления MSDN
  3. MSDN FlagsAttribute
  4. Не ожидается возвращаемое значение побитового приведения перечисления

Для полноты картины я изменяю свой исходный вопрос с правильным использованием из ответа @kirk-woll

Исправленный синтаксис DWORD

    /// <summary>
    /// How to compare filter values; values explicitly defined with *correct* DWORD style
    /// </summary>
    [Flags]
    public enum ComparisonsDwordCorrectlyDefined {
        [Description("x")]
        None = 0x0
        ,
        [Description("!=")]
        NotEqual = 0x1
        ,
        [Description("=")]
        Equal = 0x2
        ,
        [Description(">")]
        GreaterThan = 0x4
        ,
        [Description("<")]
        LessThan = 0x8
        ,
        /// <summary>
        /// Combination of <see cref="Equal"/> and <see cref="LessThan"/>
        /// </summary>
        [Description("<=")]
        LessThanOrEqual = (Equal | LessThan)
        ,
        /// <summary>
        /// Combination of <see cref="Equal"/> and <see cref="GreaterThan"/>
        /// </summary>
        [Description(">=")]
        GreaterThanOrEqual = (Equal | GreaterThan)
    }//--   enum    ComparisonsDwordCorrectlyDefined

Вывод из перечисления

    Flag enum, explicit values with correct DWORD ----
    Enum = None             ,        Descr = x,     Value = 0
    Enum = NotEqual         ,        Descr = !=,    Value = 1
    Enum = Equal            ,        Descr = =,     Value = 2
    Enum = GreaterThan      ,        Descr = >,     Value = 4
    Enum = GreaterThanOrEqual,       Descr = >=,    Value = 6
    Enum = LessThan         ,        Descr = <,     Value = 8
    Enum = LessThanOrEqual  ,        Descr = <=,    Value = 10

person drzaus    schedule 21.06.2012    source источник
comment
Поскольку это немного эзотерично, весь смысл вопроса в том, что, хотя я могу просто быть в безопасности и использовать побитовое смещение, я предполагаю, что использовать операцию немного менее эффективно, чем просто объявить значение (а синтаксис DWORD проще чем его явно умножить)   -  person drzaus    schedule 21.06.2012
comment
Вы хотите сказать, что поведение VS2012 RC отличается от, скажем, VS 2010? Если да, то это может быть ошибка компилятора VS2012 / C #. Сообщите в MSFT быстро!   -  person dthorpe    schedule 21.06.2012


Ответы (1)


Ваш шестнадцатеричный стиль DWORD неверен. Вы увеличиваете на единицу, а не удваиваете:

NotEqual = 0x1
Equal = 0x2
GreaterThan = 0x3
LessThan = 0x4

Должно быть:

NotEqual = 0x1
Equal = 0x2
GreaterThan = 0x4
LessThan = 0x8

Конечно, использование шестнадцатеричного стиля бесполезно, пока вы не достигнете уровня выше 8:

LessThan = 0x8
GreaterThanOrEqual = 0x10
LessThanOrEqual = 0x20

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

person Kirk Woll    schedule 21.06.2012
comment
Согласованный; Я определенно просто забыл / пропустил удвоение во всех примерах, которые когда-либо видел / использовал. И хотя два - четыре - восемь легко запомнить, я думаю, именно поэтому битовый сдвиг выглядел так привлекательно, поскольку вы просто увеличиваете сдвиг. - person drzaus; 21.06.2012
comment
Действительно? Мне нужно подождать еще 8 минут, чтобы отметить это как ответ? - person drzaus; 21.06.2012