Kit*_*Kit 15 .net c# extension-methods design-patterns anti-patterns
我经常看到并使用带有附加属性的枚举来做一些基本的事情,比如提供显示名称或描述:
public enum Movement {
[DisplayName("Turned Right")]
TurnedRight,
[DisplayName("Turned Left")]
[Description("Execute 90 degree turn to the left")]
TurnedLeft,
// ...
}
Run Code Online (Sandbox Code Playgroud)
并且有一组扩展方法来支持这些属性:
public static string GetDisplayName(this Movement movement) { ... }
public static Movement GetNextTurn(this Movement movement, ...) { ... }
Run Code Online (Sandbox Code Playgroud)
遵循此模式,可以将其他现有或自定义属性应用于字段以执行其他操作.它几乎就像枚举可以作为简单的枚举值类型一样工作,也可以作为一个包含许多字段的更丰富的不可变值对象:
public class Movement
{
public int Value { get; set; } // i.e. the type backing the enum
public string DisplayName { get; set; }
public string Description { get; set; }
public Movement GetNextTurn(...) { ... }
// ...
}
Run Code Online (Sandbox Code Playgroud)
通过这种方式,它可以在序列化期间作为一个简单的字段"移动",快速比较等等,但行为可以"内化"(ala OOP).
也就是说,我认识到这可能被视为一种反模式.与此同时,我的一部分认为这很有用,反对可能过于严格.
我认为这在C#中是一个糟糕的模式,因为声明和访问属性的语言支持是如此残缺; 它们并不意味着存储大量数据.用非平凡的值声明属性是一件痛苦的事,获取属性的值很痛苦.一旦你想要与你的枚举相关的远程有趣的东西(比如在枚举上计算某些东西的方法,或者包含非原始数据类型的属性),你需要将它重构为一个类或者把另一个东西放在一个类中.一些带外的地方.
使用一些静态实例保存相同信息的不可变类并不是更难,在我看来,它更具惯用性.
我会说这是反模式.这就是原因.让我们来看看你现有的枚举(为简洁起见剥离属性):
public enum Movement
{
TurnedRight,
TurnedLeft,
Stopped,
Started
}
Run Code Online (Sandbox Code Playgroud)
现在,让我们说需要扩展到更精确的东西; 比方说,标题和/或速度的变化,将"伪类"中的一个"字段"变为两个:
public sealed class Movement
{
double HeadingDelta { get; private set; }
double VelocityDelta { get; private set; }
// other methods here
}
Run Code Online (Sandbox Code Playgroud)
所以,你有一个编码的枚举,现在必须转换成一个不可变的类,因为你现在跟踪两个正交(但仍然是不可变的)属性,这些属性确实属于同一个接口.你写的针对"丰富的枚举"的任何代码现在必须被彻底删除和重写; 然而,如果你开始作为一个班级,你可能会做更少的工作.
您必须询问代码将如何在一段时间内得到维护,以及丰富的枚举是否比类更易于维护.我敢打赌,它不会更易于维护.此外,正如mquander所指出的那样,基于类的方法在C#中更为惯用.
还有其他需要考虑的事情.如果对象是不可变的struct而不是a class,那么您将获得相同的按值传递语义,并且序列化大小和对象的运行时大小可以忽略不计,就像使用枚举一样.