C#中的私有值标记枚举

时间:2011-03-31 18:41:52

标签: c# design-patterns enums flags

我在C#中创建了一个标志枚举,类似于以下内容:

    [Flags]
    public enum DriversLicenseFlags
    {
        None = 0,

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

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

关于我的意图的一些注释:

  • SuspendedRevoked是唯一可以但不一定会导致恢复的状态。
  • Restored只有在用户也是SuspendedRevoked(或两者)的情况下才有可能。重要的是要具体跟踪哪个事件是恢复的前兆。
  • 许可证可以是SuspendedRevoked(以及Restored

另外,我正在努力坚持在MSDN Designing Flags Enumerations中提出的建议。特别是:

  • 考虑为常用的标记组合提供特殊的枚举值。
    • SuspendedAndRestoredRevokedAndRestored都会很常见。
  • 当某些值组合无效时,请避免创建标记枚举。
    • 这是我的问题,因为Restored无效,除非至少设置了SuspendedRevoked中的一个。

理想情况下,我希望Restored的值存在于内部使用的枚举中,但只能通过某些有效组合公开设置。不幸的是,internal不是枚举值的有效修饰符。

我想过几个替代品,但每个都有缺点:

  1. Restored保留为公共值,请注意注释中的限制,并对公共API上的无效组合进行前置条件检查。

    这样可行,很可能是我将采用的解决方案。但是,似乎他们应该是一个更清洁的解决方案。

  2. 使用here所述的增强的类似java的枚举,并将Restored定义为internal static

    这也可行,但感觉有点矫枉过正,因为此时我不需要任何其他功能。

  3. 不要将Restored定义为值,而是保留OR的值,并检查使用方法中的值。即:

    internal const int RestoredFlag = 1 << 3;
    [Flags]
    public enum DriversLicenseFlags
    {
        None = 0,
    
        Suspended = 1 << 1,
        Revoked   = 1 << 2,
    
        SuspendedAndRestored = Suspended | RestoredFlag,
        RevokedAndRestored   = Revoked   | RestoredFlag,
    }
    
  4. 无论是如何定义以及如何在内部使用它,这都让我感到很难过。

3 个答案:

答案 0 :(得分:2)

为什么要特别使用Flags?这不完全是你追求的吗?

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

确保只在DriversLicenseStatus属性的属性设置器中进行“有效”转换肯定会很容易。

如果由于某种原因你肯定想在内部使用Flags,那么你可以定义一个单独的private enum DriversLicenseStatusFlags并从中转换为仅在公共接口中公开DriversLicenseStatus值。

另一个值得考虑的选择是将枚举分为两个值:

bool IsActive;

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

“AndRestored”案例将是那些IsActive == trueInactiveReason != InactiveReason.None的案例。

我得到的明显印象是你在这里过度工程。 :)

答案 1 :(得分:1)

选项3对我来说似乎是一个好主意。请记住,.NET枚举在设置为未定义的值时不会抛出异常,因此没有任何东西阻止用户说:

PrintDriversLicenseStatus(42);

...其中PrintDriversLicenseStatusDriversLicenseFlags作为参数。

所以你需要让每个方法检查它的输入,以确保它们是一个定义的值。

修改

另一个需要考虑的选项:创建一个特殊的内部枚举类,并将其转换为内部使用:

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

不确定这是否更好,但可能感觉不那么苛刻。

答案 2 :(得分:0)

我会去标志并创建验证方法

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

如果你能忍受它违反微软的建议。我认为这是一个边缘案例。