DateTimeOffset (Strict)

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:


 // Local and Unspecified are both treated as Local public static implicit operator DateTimeOffset (DateTime dateTime); 

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!


 /// <summary> /// Same as <see cref="DateTimeOffset"/> /// but w/o implicit conversion from <see cref="DateTime"/> /// </summary> public readonly struct DateTimeOffsetStrict { private DateTimeOffset Internal { get; } private DateTimeOffsetStrict(DateTimeOffset @internal) { Internal = @internal; } public static implicit operator DateTimeOffsetStrict(DateTimeOffset dto) => new DateTimeOffsetStrict(dto); public static implicit operator DateTimeOffset(DateTimeOffsetStrict strict) => strict.Internal; } 

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.

Source: https://habr.com/ru/post/438946/


All Articles