C#:Enum反模式

时间:2010-10-12 10:09:44

标签: c# .net design-patterns enums anti-patterns

有人一直在谈论Enums一般违反清洁守则原则,所以我正在寻找人们最喜欢的Enum反模式和替代解决方案。

例如,我见过这样的代码:

switch(enumValue) {
    case myEnum.Value1:
        // ...
        break;
    case myEnum.Value2:
        // ...
        break;
}

它比带有魔术字符串的switch语句更好一步,但这可能是用工厂,容器或其他模式更好地解决的。

甚至是这样的老派代码:

if(enumValue == myEnum.Value1) {
   // ...
} else if (enumValue == myEnum.Value2) {
   // ...
}

您在枚举方面遇到了哪些其他反模式和更好的实施

5 个答案:

答案 0 :(得分:11)

我认为Enums非常有用。我为Enum写了一些扩展,为其使用增加了更多的价值

首先,有描述扩展方法

public static class EnumExtensions
{
    public static string Description(this Enum value)
    {
        var entries = value.ToString().Split(ENUM_SEPERATOR_CHARACTER);
        var description = new string[entries.Length];
        for (var i = 0; i < entries.Length; i++)
        {
            var fieldInfo = value.GetType().GetField(entries[i].Trim());
            var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
            description[i] = (attributes.Length > 0) ? attributes[0].Description : entries[i].Trim();
        }
        return String.Join(", ", description);
    }
    private const char ENUM_SEPERATOR_CHARACTER = ',';
}

这将允许我像这样定义en enum:

 public enum MeasurementUnitType
 {
    [Description("px")]
    Pixels = 0,
    [Description("em")]
    Em = 1,
    [Description("%")]
    Percent = 2,
    [Description("pt")]
    Points = 3
 }

通过这样做获得标签:var myLabel = rectangle.widthunit.Description()(不需要switch语句。)

如果rectangle.widthunit = MeasurementUnitType.Pixels,它将返回“px”,如果rectangle.widthunit = MeasurementUnitType.Pixels | MeasurementUnitType.Em,它将返回“px,em”。

然后,有一个

    public static IEnumerable<int> GetIntBasedEnumMembers(Type @enum)
    {
        foreach (FieldInfo fi in @enum.GetFields(BindingFlags.Public | BindingFlags.Static))
            yield return (int)fi.GetRawConstantValue();
    }

这将让我遍历任何基于int的值的枚举,并返回int值。

我发现这些在一个已经很有用的概念中非常有用。

答案 1 :(得分:1)

我认为将两个切换语句作为非OO设计as explained further in this answer的症状。

答案 2 :(得分:1)

这不是答案,而是对Enum反模式列表的贡献。

在今天早上的代码审核期间,我遇到了类似于以下内容的案例,所有案例都在同一个类中。

两种情况:

  1. 喝酒前
  2. 喝酒后
  3. ...

        public enum ListEnum 
        { 
            CategoryOne,
            CategoryTwo,
            CategoryThree,
            CategoryFour
        }
    
    
        public class UIELementType
        {
            public const string FactoryDomain = "FactoryDomain";
            public const string Attributes = "Attributes";
        }
    

答案 3 :(得分:0)

使用非反模式的枚举。在一些关于重构的书中,这段代码用于演示如何用多态替换它。当你在代码中过度使用枚举时就可以了。

答案 4 :(得分:0)

这一切都取决于您对枚举的处理方式。

  1. 如果您试图阻止开发人员将魔术数字传递到您的操作中,并且希望保持数据库的数据参照完整性不变,那么,是的!使用T4-Templates(使用ORM)转到MeasurementUnitTypes表,并生成一个ID,Name和Description列与该枚举的int,Enum_Name和Description属性匹配的枚举(对枚举@danijels的其他字段\数据的不错方法)如上所述。如果将新的Measurement Type添加到MeasurementUnitTypes表中,则可以右键单击并运行T4-Template,并为表中添加的该新行生成枚举代码。我不喜欢应用程序中未链接到数据库的硬编码数据,因此提到了T4-Template方法。否则它是不可扩展的……如果某个其他外部系统想要检索我们的系统中使用的度量标准,那么它将在系统中进行硬编码,并且您将无法通过服务将其公开给客户端。那留在那儿。

  2. 如果目的与数据无关,并且您已将逻辑分配给特定的枚举,则为否!这违反了SOLID(开放关闭原则),就像您在应用程序中的某个位置应用开关或一系列If来操作每个枚举的逻辑一样,如果您这样做真的很糟糕,那么这些开关或If遍及整个过程....祝您好运,并添加了一个新的枚举...因此,根据SOLID原则,它不能为扩展而开放,而不能为修改而封闭,因为您需要修改现有代码。

    如果您选择的是2,那么我建议然后使用@danijels注释中的示例将您的枚举替换为以下内容:

    public interface IMeasurementUnitType
    {
        int ID { get; }
    
        string Description { get; }
    
        // Just added to simulate a action needed in the system
        string GetPrintMessage(int size);
    }
    

上面的代码定义了每个度量应遵循的接口(代码协定)。现在让我们定义百分比和像素测量:

    public class PixelsMeasurementUnitType : IMeasurementUnitType
    {
        public int ID => 1;

        public string Description => "Pixel";

        public string GetPrintMessage(int size)
        {
            return $"This is a {Description} Measurement that is equal to {size} pixels of the total screen size";
        }
    }

    public class PercentMeasurementUnitType : IMeasurementUnitType
    {
        public int ID => 2;

        public string Description => "Persentage";

        public string GetPrintMessage(int size)
        {
            return $"This is a {Description} Measurement that is equal to {size} persent of total screen size (100)";
        }
    }

所以我们定义了两种类型,我们将在代码中使用它们,如下所示:

    var listOfMeasurmentTypes = AppDomain.CurrentDomain.GetAssemblies()
                        .SelectMany(s => s.GetTypes())
                        .Where(p => typeof(IMeasurementUnitType).IsAssignableFrom(p) 
                                    && !p.IsInterface)
                        .ToList();

在这里,我们获取所有扩展IMeasurementUnitType接口的类型,而不是接口本身。现在,我们可以使用Activator创建类的实例来填充我们的UI控件:

    public IEnumerable<IMeasurementUnitType> GetInstantiatedClassesFromTypes(List<Type> types)
    {
        foreach (var type in types)
        {
            yield return (IMeasurementUnitType)Activator.CreateInstance(type);
        }
    }

您可以将上面的代码更改为任何类型的通用代码,并且现在发生寿命,并且客户将新的称为Point的测量单位类型指定为新要求,我不需要更改任何代码,只需添加新代码类型(扩展代码,不要修改)。新类型将自动在应用程序中显示。

    public class PointMeasurementUnitType : IMeasurementUnitType
    {
        public int ID => 3;

        public string Description => "Point";

        public string GetPrintMessage(int size)
        {
            return $"This is a {Description} Measurement that is equal to {size} points of total screen size";
        }
    }

一个好主意是在启动应用程序时尝试缓存您的类型以提高性能,或者尝试使用您选择的DI容器。

另外,有人可能会争辩说,您需要在应用程序中的某个地方区分类型,我同意,但是您希望苹果与苹果并驾齐驱。因此,请尽量尝试应用与该类型相同的原理。如果在某种类型的图形处理器(例如)类中使用此类型,请使用IGraphicsProcessor并让您的具体类区分这些类型,例如PersentageAndPixelGraphicsProcessor(从IGraphicsProcessor扩展而来),或者仅区分一种类型,则称为PersentageGraphicsProcessor。

对不起,HUGE SA很抱歉,但是我真的很喜欢enum,但是当您尝试使用enum分离逻辑时,我觉得它是很强的反模式。

欢迎评论