将整数标识符映射到枚举有什么缺点?


16

我一直在考虑为这样的标识符创建自定义类型:

public enum CustomerId : int { /* intentionally empty */ }
public enum OrderId : int { }
public enum ProductId : int { }

我这样做的主要动机是防止在您无意中将orderItemId传递给期望有orderItemDetailId的函数时发生的错误。

枚举似乎可以与我想要在典型的.NET Web应用程序中使用的所有内容无缝地结合在一起:

  • MVC路由工作正常
  • JSON序列化工作正常
  • 我能想到的每个ORM都可以正常工作

所以现在我想知道,“为什么不应该这样做?” 这些是我能想到的唯一缺点:

  • 这可能会使其他开发人员感到困惑
  • 如果您有任何非整数标识符,它将在您的系统中引起不一致。
  • 它可能需要额外的转换,例如(CustomerId)42。但是我认为这不会成为问题,因为ORM和MVC路由通常会直接将枚举类型的值交给您。

所以我的问题是,我想念什么?这可能是个坏主意,但是为什么呢?



3
这称为“微型打字”,您可以在Google周围搜索(例如,是针对Java的,细节不同,但动机相同)
qbd

我输入了两个部分答案,然后删除了它们,因为我意识到自己正在考虑这个错误。我同意您的看法,“这可能是个坏主意,但是为什么呢?”
user2023861'2

Answers:


7

为每种标识符类型创建类型是一个好主意,主要是出于您陈述的原因。我不会通过将类型实现为枚举来做到这一点,因为将其设置为适当的结构或类将为您提供更多选择:

  • 您可以将隐式转换添加到int。当您考虑它时,这是另一个方向,即int-> CustomerId,这是有问题的一个,应得到消费者的明确确认。
  • 您可以添加业务规则,以指定可接受的标识符和不可接受的标识符。检查int是否为正不仅是一个微不足道的检查:有时,所有可能的ID的列表都保存在数据库中,而是仅在部署期间进行更改。然后,您可以根据适当查询的缓存结果轻松检查给定的ID是否存在。这样,您可以保证在存在无效数据的情况下应用程序将快速失败
  • 标识符上的方法可以帮助您进行昂贵的数据库存在检查或获取相应的实体/域对象。

您可以在《功能性C#:原始痴迷》一文中了解有关实现此功能的信息


1
我要说的是,几乎可以肯定,您不想将每个int包装在一个类中,而是将其包装在struct
jk中。

很好的说明,我稍微更新了答案。
卢卡斯·兰斯基

3

这是一个聪明的技巧,但仍然是一种骇人听闻的技巧,因为它滥用了某项功能,而这并不是预期的目的。骇客在可维护性方面付出了代价,因此仅凭不足之处您就无法发现任何其他弊端,与解决该问题的惯用方式相比,它还需要具有显着的优势

惯用的方法是使用单个字段创建包装器类型或结构。这将以更明确和惯用的方式为您提供相同的类型安全性。尽管您的建议很聪明,但是使用包装器类型并没有任何明显的好处。


1
枚举的主要优点是它可以按照您希望的方式“按需”使用MVC,序列化,ORM等。过去我创建了自定义类型(结构和类),最终编写的代码比您编写的要多为了使这些框架/库可以与您的自定义类型一起使用,必须这样做。
default.kramer

序列化应该以任何一种方式透明地工作,例如。您确实需要配置ORM,以进行某种显式转换。但这是强类型语言的代价。
JacquesB '16

2

从我的观点来看,这个主意很好。将不同类型的标识符赋予不同类型的标识符,或者通过类型来表达语义并让编译器进行检查的意图是一个很好的选择。

我不知道它在.NET性能方面如何工作,例如枚举值是否必须装箱。无论如何,与数据库一起使用的OTOH代码很可能是受I / O约束的,装箱的标识符不会成为瓶颈。

在一个完美的世界中,标识符将是不可变的,并且仅在相等时才具有可比性。实际字节将是实现细节。不相关的标识符将具有不兼容的类型,因此您不能错误地使用一个标识符。您的解决方案几乎符合要求。


-1

您不能这样做,枚举必须是预定义的。因此,除非您愿意预先定义所有可能的客户ID值,否则您的类型将毫无用处。

如果进行转换,您将无视防止将A型ID意外分配给B型ID的主要目标。因此,枚举对您的目标没有帮助。

但这确实提出了一个问题,尽管它有助于从Int32(或Guid)的后代创建客户ID,订单ID和产品ID的类型。我可以想到为什么这是错误的原因。

ID的目的是识别。整数类型已经非常适合此操作,通过从它们降级不再获得更多标识。不需要专业化也不需要,通过使用不同的标识符类型,您的世界不会得到更好的表示。因此,从面向对象的角度来看,这将是不好的。

根据您的建议,您不仅需要类型安全,还需要值安全,而这超出了建模领域,这是一个操作问题。


1
不,枚举不必在.NET中预定义。关键在于,几乎不会发生强制转换,例如,public ActionResult GetCustomer(CustomerId custId)不需要强制转换-MVC框架将提供价值。
default.kramer

2
我不同意 对我来说,从面向对象的角度来看,这是完全合理的。Int32没有语义,它是纯粹的“技术”类型。但是我需要抽象的Identifier类型,该类型用于标识对象,并且恰好实现为Int32(但可以很长,也可以,实际上并不重要)。我们有具体的专业化产品,例如ProductId,它来自标识产品ID的具体实例的标识符。将OrderItemId的值分配给ProductId没有逻辑上的意义(这显然是错误的),因此类型系统可以保护我们免受此侵害非常好。
qbd at 21:08

1
我不知道C#在使用枚举方面是否如此灵活。作为习惯于枚举是可能值枚举的事实(?)的开发人员,这当然使我感到困惑。它们通常指定一系列命名ID。因此,此类型在没有范围和名称的意义上被“滥用”,只剩下类型。而且显然,该类型也不是那么安全。另一件事:该示例显然是一个数据库应用程序,在某个时候,您的“枚举”将被映射到一个整数类型字段,这时您毕竟会安全地丢失假定的类型。
马丁·马特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.