Закрытое значение в перечислении флагов C # - PullRequest
6 голосов
/ 31 марта 2011

Я создаю перечисление флагов в C #, подобное следующему:

    [Flags]
    public enum DriversLicenseFlags
    {
        None = 0,

        Suspended = 1 << 1,
        Revoked   = 1 << 2,

        Restored  = 1 << 3,
        SuspendedAndRestored = Suspended | Restored,
        RevokedAndRestored   = Revoked   | Restored,
    }

Пара замечаний о моих намерениях здесь:

  • Suspended и Revoked - уникальные состояния, которые могут, но не обязательно, привести к восстановлению.
  • Restored должно быть возможно только в том случае, если пользователь также был Suspended или Revoked (или оба). Важно точно отследить, какое событие предшествовало восстановлению.
  • Лицензия может быть как Suspended, так и RevokedRestored)

Также я пытаюсь придерживаться предложений, сделанных в MSDN Проектирование перечислений флагов . В частности:

  • Рассмотрите возможность предоставления специальных значений перечисления для часто используемых комбинаций флагов.
    • SuspendedAndRestored и RevokedAndRestored будут общими.
  • Избегайте создания перечислений флагов, когда определенные комбинации значений недопустимы.
    • Это моя проблема, потому что Restored недействителен, если не указан хотя бы один из Suspended и Revoked.

В идеале, я бы хотел, чтобы значение Restored присутствовало в перечислении для внутреннего использования, но было доступно только для публичной установки через некоторую допустимую комбинацию. К сожалению, internal не является допустимым модификатором для перечисления значений.

Я подумал о нескольких альтернативах, но у каждого, похоже, есть недостатки:

  1. Сохраните Restored в качестве общедоступного значения, отметьте ограничения в комментариях и выполните предварительную проверку на наличие недопустимых комбинаций в открытых API.

    Это бы сработало, и, скорее всего, я выберу решение. Тем не менее, похоже, что они должны быть более чистым решением.

  2. Используйте расширенный java-подобный enum, как описано здесь и определите Restored как internal static.

    Это бы тоже сработало, но ощущается как излишнее, потому что на данный момент мне не нужны никакие другие функции.

  3. Не определяйте Restored как значение, но зарезервируйте значение для OR или для проверки значения в потребляющих методах. i.e.:

    internal const int RestoredFlag = 1 << 3;
    [Flags]
    public enum DriversLicenseFlags
    {
        None = 0,
    
        Suspended = 1 << 1,
        Revoked   = 1 << 2,
    
        SuspendedAndRestored = Suspended | RestoredFlag,
        RevokedAndRestored   = Revoked   | RestoredFlag,
    }
    

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

Ответы [ 3 ]

2 голосов
/ 31 марта 2011

Почему вы хотите использовать Flags специально? Разве это не то, что вам нужно?

public enum DriversLicenseStatus
{
    None = 0,
    Suspended,
    Revoked,
    SuspendedAndRestored,
    RevokedAndRestored,
}

Несомненно, будет легко убедиться, что из «установщиков» свойств DriversLicenseStatus сделаны только «допустимые» переходы.

Если по какой-то причине вы определенно хотите использовать Flags для внутреннего использования, тогда вы можете определить отдельный private enum DriversLicenseStatusFlags и преобразовать его, чтобы отображать только значения DriversLicenseStatus в вашем общедоступном интерфейсе.

Другой вариант, который стоит рассмотреть, - это разделение enum на два значения:

bool IsActive;

enum InactiveReason {
    None = 0,
    Suspended,
    Revoked,
}

"AndRestored" - это случаи с IsActive == true и InactiveReason != InactiveReason.None.

У меня складывается отчетливое впечатление, что вы слишком перегружены. :)

1 голос
/ 31 марта 2011

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

PrintDriversLicenseStatus(42);

... где PrintDriversLicenseStatus принимает DriversLicenseFlags в качестве аргумента.

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

Редактировать

Еще один вариант для вашего рассмотрения: создайте специальный внутренний перечислимый класс и приведите к нему аргументы для внутреннего использования:

[Flags]
public enum DriversLicenseFlagsInternal
{
    None = 0,

    Suspended = 1 << 1,
    Revoked   = 1 << 2,

    Restored  = 1 << 3,
    SuspendedAndRestored = Suspended | Restored,
    RevokedAndRestored   = Revoked   | Restored,
}

[Flags]
internal enum DriversLicenseFlags
{
    None = DriversLicenseFlagsInternal.None,

    Suspended = DriversLicenseFlagsInternal.Suspended,
    Revoked   = DriversLicenseFlagsInternal.Revoked,

    SuspendedAndRestored = DriversLicenseFlagsInternal.SuspendedAndRestored,
    RevokedAndRestored   = DriversLicenseFlagsInternal.RevokedAndRestored,

}


public void DoSomething(DriversLicenseFlags arg)
{
    var argAsInternal = (DriversLicenseFlagsInternal) arg;
// or var argAsInternal = Util.CheckDefinedDriversLicense(arg);
}

Не уверен, что это лучше, но может показаться менее хакерским.

0 голосов
/ 31 марта 2011

Я бы выбрал флаги и создал метод проверки

[Flags]
public enum DriversLicenseFlags {
    None,
    Suspended,
    Revoked,
    Restored
}

public bool Validate(DriversLicenseFlags flags) {
    if(flags.HasFlag(DriversLicenseFlags.Restored)) {
        return 
            flags.HasFlag(DriversLicenseFlags.Revoked) || 
            flags.HasFlag(DriversLicenseFlags.Suspended);

        // Or throw an exception
     }
     return true;
}

Если вы можете жить с этим вопреки рекомендации Microsoft. Я вижу это как крайний случай.

...