¿Cuáles son los inconvenientes de asignar identificadores integrales a enumeraciones?

16

He estado pensando en crear tipos personalizados para identificadores como este:

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

Mi motivación principal para esto es evitar el tipo de error en el que accidentalmente pasa un orderItemId a una función que esperaba un orderItemDetailId.

Parece que las enumeraciones funcionan a la perfección con todo lo que quisiera usar en una aplicación web .NET típica:

  • El enrutamiento MVC funciona bien
  • La serialización JSON funciona bien
  • Cada ORM que se me ocurra funciona bien

Así que ahora me pregunto, "¿por qué no debería hacer esto?" Estos son los únicos inconvenientes que se me ocurren:

  • Puede confundir a otros desarrolladores.
  • Introduce inconsistencias en su sistema si tiene identificadores no integrales.
  • Puede requerir fundición adicional, como (CustomerId)42. Pero no creo que esto sea un problema, ya que el enrutamiento ORM y MVC generalmente le entregará valores del tipo de enumeración directamente.

Entonces mi pregunta es, ¿qué me estoy perdiendo? Esta es probablemente una mala idea, pero ¿por qué?

default.kramer
fuente
3
Esto se llama "microtipado", puede buscar en Google (por ejemplo, esto es para Java, los detalles son diferentes, pero la misma motivación)
qbd
Escribí dos respuestas parciales y luego las eliminé porque me di cuenta de que estaba pensando en esto mal. Estoy de acuerdo con usted en que "esta es probablemente una mala idea, pero ¿por qué?"
user2023861

Respuestas:

7

Es una gran idea crear tipos para cada tipo de identificador, principalmente por las razones que usted indicó. No lo haría implementando el tipo como enum porque hacerlo una estructura o clase adecuada le brinda más opciones:

  • Puede agregar la conversión implícita a int. Cuando lo piensa, es la otra dirección, int -> CustomerId, esa es la problemática que merece la confirmación explícita del consumidor.
  • Puede agregar reglas comerciales que especifiquen qué identificadores son aceptables y cuáles no. No se trata solo de una comprobación débil de si el int es positivo: a veces, la lista de todas las ID posibles se mantiene en la base de datos, sino que cambia solo durante la implementación. Luego, puede verificar fácilmente que la ID dada exista contra el resultado en caché de la consulta adecuada. De esta manera, puede garantizar que su aplicación fallará rápidamente en presencia de datos no válidos.
  • Los métodos en el identificador pueden ayudarlo a realizar costosas comprobaciones de existencia de DB u obtener el objeto de entidad / dominio correspondiente.

Puede leer sobre la implementación de esto en el artículo Funcional C #: obsesión primitiva .

Lukáš Lánský
fuente
1
Yo diría que es casi seguro que no quiere envolver cada int en una clase sino en una estructura
jk.
Buen comentario, actualicé la respuesta ligeramente.
Lukáš Lánský
3

Es un truco inteligente, pero aún así un truco en el que se abusa de una característica para algo que no es el propósito previsto. Los hacks tienen un costo en mantenimiento, por lo que no es suficiente con que no pueda encontrar otros inconvenientes, también debe tener beneficios significativos en comparación con la forma más idiomática de resolver el mismo problema.

La forma idiomática sería crear un tipo de envoltura o estructura con un solo campo. Esto le dará la misma seguridad de tipo de una manera más explícita e idiomática. Si bien su propuesta es ciertamente inteligente, no veo ninguna ventaja significativa al usar tipos de contenedor.

JacquesB
fuente
1
La principal ventaja de una enumeración es que "simplemente funciona" de la manera que desea con MVC, serialización, ORM, etc. He creado tipos personalizados (estructuras y clases) en el pasado y termina escribiendo más código que usted. debería hacerlo para que esos marcos / bibliotecas funcionen con sus tipos personalizados.
default.kramer
La serialización debería funcionar de manera transparente de cualquier manera, pero por ejemplo. Los ORM que necesita para configurar algún tipo de conversión explícita. Pero este es el precio de un lenguaje fuertemente tipado.
JacquesB
2

Desde mi punto de vista, la idea es buena. La intención de dar diferentes tipos a clases de identificadores no relacionados, y de lo contrario expresar la semántica a través de tipos y dejar que el compilador lo compruebe, es buena.

No sé cómo funciona en .NET en cuanto al rendimiento, por ejemplo, los valores de enumeración están necesariamente encuadrados. El código OTOH que funciona con una base de datos es probable que esté vinculado a E / S de todos modos y los identificadores en caja no serán un cuello de botella.

En un mundo perfecto, los identificadores serían inmutables y solo comparables para la igualdad; bytes reales serían un detalle de implementación. Los identificadores no relacionados tendrían tipos incompatibles, por lo que no podría usar uno por otro por error. Su solución prácticamente se ajusta a la factura.

9000
fuente
-1

No puede hacer esto, las enumeraciones deben estar predefinidas. Por lo tanto, a menos que esté dispuesto a predefinir todo el espectro de posibles valores de ID de cliente, su tipo será inútil.

Si lanzas, estarías desafiando tu objetivo principal de evitar asignar accidentalmente una identificación de tipo A a una identificación de tipo B. Entonces las enumeraciones no son útiles para su propósito.

Sin embargo, esto plantea la pregunta de si sería útil crear sus tipos para la identificación del cliente, la identificación del pedido y la identificación del producto descendiendo desde Int32 (o Guid). Puedo pensar en una razón por la cual esto estaría mal.

El propósito de una identificación es la identificación. Los tipos integrales ya son perfectos para esto, ya no se identifican más al descender de ellos. No se requiere ni se desea especialización, su mundo no se representará mejor al tener diferentes tipos de identificadores. Entonces, desde una perspectiva OO, sería malo.

Con su sugerencia, desea algo más que seguridad de tipo, desea seguridad de valor y eso está más allá del dominio de modelado, es un problema operativo.

Martin Maat
fuente
1
No, las enumeraciones no tienen que estar predefinidas en .NET. Y el punto es que la conversión casi nunca ocurre, por ejemplo, public ActionResult GetCustomer(CustomerId custId)no se necesita ninguna conversión : el marco MVC proporcionará el valor.
default.kramer
2
No estoy de acuerdo Para mí, tiene mucho sentido desde la perspectiva OO. Int32 no tiene semántica, es de tipo puramente "técnico". Pero necesito un tipo de identificador abstracto que sirva para identificar objetos y que se implemente como Int32 (pero podría ser largo o lo que sea, en realidad no importa). Tenemos especializaciones concretas como ProductId que descienden del identificador que identifica la instancia concreta de ProductId. No tiene ningún sentido lógico asignar el valor de OrderItemId a ProductId (es claramente un error), por lo que es genial que el sistema de tipos nos pueda proteger de esto.
qbd
1
No sabía que C # era tan flexible con respecto al uso de enumeraciones. Ciertamente es confuso para mí como desarrollador acostumbrado al hecho (?) Que las enumeraciones son, bueno, enumeraciones de posibles valores. Por lo general, especifican un rango de identificadores con nombre. Así que ahora este tipo es "abusado" en el sentido de que no hay rango y no hay nombres, solo queda el tipo. Y aparentemente, el tipo tampoco es tan seguro. Otra cosa: el ejemplo es obviamente una aplicación de base de datos y, en algún momento, su "enumeración" se asignará a un campo de tipo integral, en cuyo momento perderá su supuesto tipo de forma segura después de todo.
Martin Maat