Каковы недостатки в отображении интегральных идентификаторов в перечисления?


16

Я думал о создании пользовательских типов для идентификаторов, как это:

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

Моя основная мотивация для этого состоит в том, чтобы предотвратить ошибку, при которой вы случайно передаете orderItemId функции, ожидающей orderItemDetailId.

Кажется, перечисления работают без проблем со всем, что я хотел бы использовать в типичном веб-приложении .NET:

  • MVC роутинг работает нормально
  • JSON-сериализация работает отлично
  • Каждый ORM, о котором я могу думать, прекрасно работает

Так что теперь я задаюсь вопросом: «Почему я не должен это делать?» Это единственные недостатки, о которых я могу думать:

  • Это может смутить других разработчиков
  • Это вносит несоответствие в вашу систему, если у вас есть неинтегральные идентификаторы.
  • Это может потребовать дополнительного литья, вроде (CustomerId)42. Но я не думаю, что это будет проблемой, поскольку маршрутизация ORM и MVC обычно передает вам значения типа enum напрямую.

Итак, мой вопрос: чего мне не хватает? Вероятно, это плохая идея, но почему?



3
Это называется «микротипирование», вы можете погуглить (например, это для Java, детали разные, но мотивация та же)
qbd

Я набрал два частичных ответа, а затем удалил их, потому что понял, что думаю об этом неправильно. Я согласен с вами, что "Это, вероятно, плохая идея, но почему?"
user2023861

Ответы:


7

Отличная идея - создать тип для каждого типа идентификатора, в основном по указанным вами причинам. Я бы не стал делать это, реализуя тип как enum, потому что создание правильной структуры или класса дает вам больше возможностей:

  • Вы можете добавить неявное преобразование в int. Когда вы думаете об этом, это другое направление, int -> CustomerId, которое является проблематичным и заслуживает явного подтверждения со стороны потребителя.
  • Вы можете добавить бизнес-правила, которые определяют, какие идентификаторы являются приемлемыми, а какие - нет. Это не просто слабая проверка, является ли int положительным: иногда список всех возможных идентификаторов сохраняется в базе данных, но изменяется только во время развертывания. Затем вы можете легко проверить, что данный идентификатор существует по кешированному результату правильного запроса. Таким образом, вы можете гарантировать, что ваше приложение быстро выйдет из строя при наличии неверных данных.
  • Методы идентификатора могут помочь вам выполнить дорогостоящие проверки существования БД или получить соответствующий объект сущности / домена.

Вы можете прочитать о реализации этого в статье Функциональный C #: Примитивная одержимость .


1
я бы сказал, что вы почти наверняка не хотите оборачивать каждое int в классе, но в struct
jk.

Хорошее замечание, я немного обновил ответ.
Лукаш Лански

3

Это умный взлом, но все же взлом в том, что он использует функцию для чего-то, что не является его целью. Хаки имеют затраты на ремонтопригодность, поэтому недостаточно, чтобы вы не могли найти никаких других недостатков, также необходимо иметь значительные преимущества по сравнению с более идиоматическим способом решения той же проблемы.

Идиоматическим способом было бы создать тип или структуру оболочки с одним полем. Это обеспечит вам такую ​​же безопасность типов более явным и идиоматическим способом. Хотя ваше предложение по общему признанию умно, я не вижу каких-либо существенных преимуществ в использовании типов обертки.


1
Основным преимуществом enum является то, что он «просто работает» так, как вы хотите, с MVC, сериализацией, ORM и т. Д. В прошлом я создавал пользовательские типы (структуры и классы), и в итоге вы пишете больше кода, чем вы. должно быть, чтобы эти фреймворки / библиотеки работали с вашими пользовательскими типами.
default.kramer

Сериализация должна работать прозрачно в любом случае, но, например, для. ORM вам нужно настроить какое-то явное преобразование. Но это цена строго типизированного языка.
JacquesB

2

Из моего POV идея хорошая. Хорошее намерение дать разные типы несвязанным классам идентификаторов и иным образом выразить семантику через типы и позволить компилятору проверить это.

Я не знаю, как это работает в .NET с точки зрения производительности, например, значения перечисления обязательно помещены в квадрат. Код OTOH, который работает с базой данных, скорее всего, связан с вводом / выводом, и идентификаторы в штучной упаковке не будут узким местом.

В идеальном мире идентификаторы были бы неизменными и сопоставимыми только по равенству; фактические байты будут деталями реализации. Несвязанные идентификаторы будут иметь несовместимые типы, поэтому вы не можете использовать один для другого по ошибке. Ваше решение практически отвечает всем требованиям.


-1

Вы не можете сделать это, перечисления должны быть предопределены. Поэтому, если вы не готовы заранее определить весь спектр возможных значений идентификаторов клиентов, ваш тип будет бесполезен.

Если вы разыгрываете, вы бы бросили вызов своей основной цели - предотвратить случайное присвоение идентификатора типа A идентификатору типа B. Таким образом, перечисления не помогают вашей цели.

Это поднимает вопрос, хотя, если было бы полезно создать ваши типы для идентификатора клиента, идентификатора заказа и идентификатора продукта, убираясь от Int32 (или Guid). Я могу думать о причинах, почему это было бы неправильно.

Целью идентификатора является идентификация. Интегральные типы уже идеально подходят для этого, они не получают больше идентификации, спускаясь с них. Специализация не требуется и не нужна, ваш мир не будет лучше представлен различными типами идентификаторов. Так что с точки зрения ОО это было бы плохо.

С вашим предложением вы хотите больше, чем безопасность типов, вы хотите ценить безопасность, и это выходит за рамки области моделирования, что является эксплуатационной проблемой.


1
Нет, перечисления не должны быть предварительно определены в .NET. И дело в том, что приведение вряд ли когда-либо произойдет, например, в public ActionResult GetCustomer(CustomerId custId)приведении не требуется - структура MVC предоставит значение.
default.kramer

2
Я не согласна Для меня это имеет смысл с точки зрения ОО. Int32 не имеет семантики, это чисто «технический» тип. Но мне нужен абстрактный тип идентификатора, который служит для идентификации объектов и который реализован как Int32 (но может быть длинным или чем-то другим, на самом деле не имеет значения). У нас есть конкретные специализации, такие как ProductId, которые происходят от идентификатора, который идентифицирует конкретный экземпляр ProductId. Не имеет никакого логического смысла присваивать значение OrderItemId для ProductId (это явно ошибка), поэтому здорово, что система типов может защитить нас от этого.
QBD

1
Я не знал, что C # был настолько гибок в отношении использования перечислений. Меня, как разработчика, смущает тот факт (?), Что перечисления - это перечисления возможных значений. Обычно они указывают диапазон именованных идентификаторов. Так что теперь этот тип «злоупотреблен» в том смысле, что в нем нет диапазона и нет имен, остается только тип. И, видимо, тип не так уж безопасен. Другое дело: пример, очевидно, является приложением базы данных, и в какой-то момент ваше enum будет отображено в поле целочисленного типа, и в этот момент вы в конце концов потеряете свой предполагаемый тип.
Мартин Маат
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.