DateTimeOffset (Strict)

Hoje de manhã, meu amigo Kirillkos encontrou um problema.


Código do problema


Aqui está o código dele:


class Event { public string Message {get;set;} public DateTime EventTime {get;set;} } interface IEventProvider { IEnumerable<Event> GetEvents(); } 

E muitas implementações do IEventProvider que IEventProvider dados de diferentes tabelas e bancos de dados.


Problema : em todas essas bases, tudo está em diferentes fusos horários. Assim, ao tentar gerar eventos para a interface do usuário, tudo fica terrivelmente confuso.


Glória a Halesberg, temos tipos, que eles possam nos salvar!


Tentativa 1


 class Event { public string Message {get;set;} public DateTimeOffset EventTime {get;set; } } 

DateTimeOffset ótimo tipo; armazena informações de deslocamento sobre o UTC. É perfeitamente suportado pelo MS SQL e Entity Framework (e na versão 6.3 será suportado ainda melhor ). No nosso estilo de código, é obrigatório para todos os novos códigos.


Agora podemos coletar informações desses mesmos provedores e, consistentemente, dependendo dos tipos, exibir tudo na interface do usuário. Vitória!


Problema : DateTimeOffset pode converter implicitamente de DateTime .
O código a seguir compila bem:


 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!"}, }; } 

Isso ocorre porque DateTimeOffset um operador de conversão implícito definido:


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

Não é disso que precisamos. Só queríamos que o programador pensasse, ao escrever o código: "Mas em que fuso horário esse evento aconteceu?" De onde vem a zona? Frequentemente de campos completamente diferentes, às vezes de tabelas relacionadas. E então cometer um erro sem pensar com muita facilidade.


Conversões implícitas condenadas!


Tentativa 2


Desde que eu ouvi sobre um martelo analisadores estáticos , tudo me parece unhas casos adequados para eles. Precisamos escrever um analisador estático que proíba essa conversão implícita e explique por que ... Parece muito trabalho. Enfim, o trabalho do compilador é verificar os tipos. Por enquanto, coloque essa ideia como detalhada.


Tentativa 3


Agora, se estivéssemos no mundo do F #, disse Kirillkos .
Nós então:


 type DateTimeOffsetStrict = Value of DateTimeOffset 

E mais não inventou improvisar algum tipo de magia nos salvaria. É uma pena que eles não escrevam F # em nosso escritório e também não conhecemos Kirillkos :-)


Tentativa 4


Não é possível fazer algo em c #? É possível, mas você é atormentado pela conversão para frente e para trás. Pare, mas acabamos de ver como você pode fazer conversões 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; } 

O mais interessante sobre esse tipo é que ele é convertido implicitamente para frente e para trás no DateTimeOffset , mas uma tentativa de convertê-lo implicitamente no DateTime causa um erro de compilação, as conversões do DateTime são possíveis apenas explicitamente. O compilador não pode chamar a "cadeia" de conversões implícitas; se elas estiverem definidas em nosso código, o padrão o proíbe ( citação de SO ). Ou seja, funciona assim:


 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!"}, }; } 

mas não assim:


 IEnumerable<Event> GetEvents() { return new[] { new Event() {EventTime = DateTime.Now, Message = "Hello from unknown time!"}, }; } 

O que precisávamos!


Sumário


Ainda não sabemos se vamos implementá-lo. Apenas todos estão acostumados ao DateTimeOffset, e agora substituí-lo pelo nosso tipo é burro. Sim, e com certeza surgirão problemas no nível da ligação de parâmetro EF, ASP.NET e em milhares de lugares. Mas a solução em si parece interessante para mim. Usei truques semelhantes para monitorar a segurança da entrada do usuário - UnsafeHtml o tipo UnsafeHtml , que é implicitamente convertido de uma string, mas você pode convertê-lo novamente em uma string ou IHtmlString apenas chamando sanitizer.

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


All Articles