C#:枚举反模式

发布于 2024-09-27 06:12:58 字数 1435 浏览 8 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

平生欢 2024-10-04 06:12:58

我认为枚举非常有用。我已经为 Enum 编写了一些扩展,这些扩展为其使用增加了更多价值

首先,有 Description 扩展方法

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 = ',';
}

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

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

并通过执行以下操作获取标签: var myLabel =矩形.widthunit.Description()(消除了对 switch 语句的任何需要)。

顺便说一句,如果rectangle.widthunit =MeasurementUnitType.Pixels,这将返回“px”,或者如果rectangle.widthunit =MeasurementUnitType.Pixels | 它将返回“px,em”。测量单位类型.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 值本身。

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

I think Enums are quite useful. I've written a few extensions for Enum that have added even more value to its use

First, there's the Description extension method

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 = ',';
}

This will allow me to define en enum like this:

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

And get the label by doing this: var myLabel = rectangle.widthunit.Description() (eliminating any need for a switch statement).

This will btw return "px" if rectangle.widthunit = MeasurementUnitType.Pixels or it will return "px,em" if rectangle.widthunit = MeasurementUnitType.Pixels | MeasurementUnitType.Em.

Then, there is a

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

Which will let me traverse any enum with int based values and return the int values themselves.

I find these to be very useful in an allready useful concept.

憧憬巴黎街头的黎明 2024-10-04 06:12:58

这完全取决于您尝试对枚举执行什么操作。

  1. 如果您试图阻止开发人员将幻数传递到您的操作中,并且您希望保持数据库的数据引用完整性完整,那么,是的!使用 T4-Templates(使用您的 ORM)转到您的MeasurementUnitTypes 表并生成一个枚举,其中 ID、名称和描述列与枚举' int、Enum_Name 和描述属性匹配(将附加字段\数据添加到枚举 @danijels 的好方法)正如上面所建议的。如果您将新的测量类型添加到MeasurementUnitTypes 表中,您只需右键单击并运行T4 模板,就会为表中添加的新行生成枚举代码。我不喜欢应用程序中的硬编码数据不链接到我的数据库,因此提到了 T4 模板方法。否则它是不可扩展的......如果其他外部系统想要检索我们系统中使用的测量标准,那么它是硬编码在系统中的,您无法通过服务将其公开给客户端。就这样离开了。

  2. 如果目的与数据无关,并且您有一些逻辑分配给特定的枚举,那么不!这违反了 SOLID(开闭原则),因为您会在应用程序中的某个地方应用一个开关或一堆 If 来操作每个枚举的逻辑,而且如果您做得非常糟糕,这些开关或 If 就会遍布整个节目......祝你好运,添加一个新的枚举...所以它不开放扩展,也不开放修改,因为您需要根据 SOLID 原则修改现有代码。

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

    公共接口 IMeasurementUnitType
    {
        int ID { 获取; }
    
        字符串描述{获取; }
    
        // 只是添加来模拟系统中所需的操作
        字符串 GetPrintMessage(int 大小);
    }
    

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

    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。

对于巨大的 SA 感到抱歉,但我真的很喜欢枚举,但是我觉得当您尝试使用枚举来分离逻辑时,这是一种强烈的反模式。

欢迎评论,

It all depends what your trying to do with the enum.

  1. If you are trying to stop your developers from passing magic numbers into your operations and you want to keep the data referential integrity intact with your DB then, YES! Use T4-Templates (using your ORM) to go to your MeasurementUnitTypes table and generate a enum with the ID, Name and Description columns matching the enum’ int, Enum_Name and Description Attribute (nice approach for additional field\data to enum @danijels) as suggested above. If you add a new Measurement Type to your MeasurementUnitTypes table you can just right click and run the T4-Template and the enum code is generated for that new row added in the table. I don’t like hard-coded data in my application that doesnt link to my DB hence the mention of the T4-Template approach. It is not extensible otherwise...what if some other external system wants to retrieve our Measurement Criteria used in our system, then it is hard-coded in the system and you can't expose it to the client via a service. That left there.

  2. If the purpose is not data related and you have some logic assigned to a specific enum then NO! this violates the SOLID (Open close principle) as you would somewhere in your application apply a switch or bunch of Ifs to action the logic per enum, ALSO if you did it REALLY bad these switches or Ifs are all over the show....good luck adding a new enum... so it is not open for extension and closed for modification as you need to modify existing code, as per the SOLID principle.

    If your choice is 2 then I suggest then to replace your enum with the following using the example from @danijels comment:

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

The above code defines the interface (code contract) that each measurement should adhere to. Now lets define Percentage and Pixel measurement :

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

        public string Description => "Pixel";

        public string GetPrintMessage(int size)
        {
            return 
quot;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 
quot;This is a {Description} Measurement that is equal to {size} persent of total screen size (100)";
        }
    }

So wee have defined two types, we would use them in code as follows:

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

Here we grab all the TYPES that extends the IMeasurementUnitType interface and NOT the interface itself. Now we can use the Activator to create instances of the classes to populate our UI controls:

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

You can change the code above to be generic for any type, AND NOW life happens and the client give a new measuring unit type called Point as a new requirement, I don't need to CHANGE ANY code, just add the new type (extend the code NOT modify). The new type will automatically be picked up in the application.

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

        public string Description => "Point";

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

a Good idea would be to cache your types for performance benefits upon starting your application or try and use a DI container of your choice.

Also, one can argue that somewhere in you application you would need to distinguish between types and I agree, however you want to keep apples with apples. So try as far as possible to apply the same principle used for this types. If this type is used in some sort of Graphics processor (for example) class then have a IGraphicsProcessor and have your concrete classes that differentiate between these types for example PersentageAndPixelGraphicsProcessor (that extends from IGraphicsProcessor) or if it distinguishes only one type call it PersentageGraphicsProcessor.

Sorry for the HUGE SA but I really like enum's however I feel when you trying to separate logic using a enums it is a STRONG anti-pattern.

comments welcome,

遗失的美好 2024-10-04 06:12:58

这不是一个答案,只是为枚举反模式列表做出了贡献。

今天早上进行代码审查时,我遇到了类似以下的情况,都在同一个类中。

两种情况:

  1. 喝酒前
  2. 喝酒后

..

    public enum ListEnum 
    { 
        CategoryOne,
        CategoryTwo,
        CategoryThree,
        CategoryFour
    }


    public class UIELementType
    {
        public const string FactoryDomain = "FactoryDomain";
        public const string Attributes = "Attributes";
    }

This isn't an answer, as much as contributing to a list of Enum anti-patterns.

During a code review this morning, I ran into a case similar to the following, all in the same class.

Two cases:

  1. Before drinking
  2. After drinking

..

    public enum ListEnum 
    { 
        CategoryOne,
        CategoryTwo,
        CategoryThree,
        CategoryFour
    }


    public class UIELementType
    {
        public const string FactoryDomain = "FactoryDomain";
        public const string Attributes = "Attributes";
    }
梦在夏天 2024-10-04 06:12:58

在非反模式中使用枚举。在一些有关重构的书籍中,此代码用于演示如何用多态性替换它。当你在代码中过度使用枚举时也没关系。

Using enums in not anti-pattern. In some books about refactoring this code is used to demonstrate how to replace it with polymorphism. It would be OK when you overuse enums in code.

寄居人 2024-10-04 06:12:58

我认为有两个 switch 语句是非面向对象设计的症状正如本答案中进一步解释的那样

I see having two switch statements as a symptom of non-OO design as explained further in this answer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文