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:
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!
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.