Esta mañana, mi amigo Kirillkos tuvo un problema.
Código de problema
Aquí está su código:
class Event { public string Message {get;set;} public DateTime EventTime {get;set;} } interface IEventProvider { IEnumerable<Event> GetEvents(); }
Y luego muchas, muchas implementaciones de IEventProvider
que IEventProvider
datos de diferentes tablas y bases de datos.
Problema : en todas estas bases, todo está en diferentes zonas horarias. En consecuencia, cuando se intenta enviar eventos a la interfaz de usuario, todo está terriblemente confundido.
Gloria a Halesberg, tenemos tipos, ¡que nos salven!
Intento 1
class Event { public string Message {get;set;} public DateTimeOffset EventTime {get;set; } }
DateTimeOffset
gran tipo; almacena información de compensación sobre UTC. Es perfectamente compatible con MS SQL y Entity Framework (y en la versión 6.3 será aún mejor ). En nuestro estilo de código, es obligatorio para todos los códigos nuevos.
Ahora podemos recopilar información de estos mismos proveedores y, consistentemente, dependiendo de los tipos, mostrar todo en la interfaz de usuario. Victoria!
Problema : DateTimeOffset
puede convertir implícitamente desde DateTime
.
El siguiente código compila bien:
class Event { public string Message {get;set;} public DateTimeOffset EventTime {get;set; } } IEnumerable<Event> GetEvents() { return new[] { new Event() {EventTime = DateTime.Now, Message = "Hello from unknown time!"}, }; }
Esto se debe a que DateTimeOffset
un operador de conversión implícito definido:
Esto no es en absoluto lo que necesitamos. Solo queríamos que el programador pensara, al escribir el código: "¿Pero en qué zona horaria ocurrió este evento?" ¿De dónde obtener la zona? A menudo de campos completamente diferentes, a veces de tablas relacionadas. Y luego cometer un error sin pensar muy fácilmente.
¡Malditas conversiones implícitas!
Intento 2
Desde que escuché sobre un martillo analizadores estáticos , todo me parece clavos estuches adecuados para ellos. Necesitamos escribir un analizador estático que prohíba esta conversión implícita y explique por qué ... Parece mucho trabajo. De todos modos, es el trabajo del compilador verificar los tipos. Por ahora, ponga esta idea como detallada.
Intento 3
Ahora, si estuviéramos en el mundo de F #, dijo Kirillkos .
Nosotros entonces:
type DateTimeOffsetStrict = Value of DateTimeOffset
Y más no se le ocurrió improvisar algún tipo de magia nos salvaría. Es una pena que no escriban F # en nuestra oficina, y tampoco conocemos realmente a Kirillkos :-)
Intento 4
¿No se puede hacer algo en C #? Es posible, pero estás atormentado al convertir de un lado a otro. ¡Detente, pero acabamos de ver cómo puedes hacer conversiones implícitas!
Lo más interesante de este tipo es que se convierte implícitamente de DateTimeOffset
, pero un intento de convertirlo implícitamente de DateTime
provocará un error de compilación, las conversiones de DateTime solo son posibles explícitamente. El compilador no puede llamar a la "cadena" de conversiones implícitas, si están definidas en nuestro código, el estándar lo prohíbe ( cita de SO ). Es decir, funciona así:
class Event { public string Message {get;set;} public DateTimeOffsetStrict EventTime {get;set; } } IEnumerable<Event> GetEvents() { return new[] { new Event() {EventTime = DateTimeOffset.Now, Message = "Hello from unknown time!"}, }; }
pero no así:
IEnumerable<Event> GetEvents() { return new[] { new Event() {EventTime = DateTime.Now, Message = "Hello from unknown time!"}, }; }
Lo que necesitábamos!
Resumen
Todavía no sabemos si lo implementaremos. Solo todos se han acostumbrado a DateTimeOffset, y ahora reemplazarlo con nuestro tipo es tonto. Sí, y seguramente surgirán problemas a nivel de enlace de parámetros EF, ASP.NET y en miles de lugares. Pero la solución en sí misma me parece interesante. Utilicé trucos similares para monitorear la seguridad de la entrada del usuario: hice el tipo UnsafeHtml
, que se convierte implícitamente de una cadena, pero puede volver a convertirlo en una cadena o IHtmlString
solo llamando al desinfectante.