DateTimeOffset (Strict)

Ce matin, mon ami Kirillkos a rencontré un problème.


Code de problème


Voici son code:


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

Et puis de nombreuses implémentations d' IEventProvider qui IEventProvider données de différentes tables et bases de données.


Problème : dans toutes ces bases, tout est dans des fuseaux horaires différents. En conséquence, lorsque vous essayez de sortir des événements dans l'interface utilisateur, tout est terriblement confus.


Gloire à Halesberg, nous avons des types, puissent-ils nous sauver!


Tentative 1


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

DateTimeOffset excellent type; il stocke des informations de décalage sur UTC. Il est parfaitement pris en charge par MS SQL et Entity Framework (et dans la version 6.3, il le sera encore mieux ). Dans notre style de code, il est obligatoire pour tout nouveau code.


Nous pouvons désormais collecter des informations auprès de ces mêmes fournisseurs et, en fonction des types, afficher tout sur l'interface utilisateur de manière cohérente. Victoire!


Problème : DateTimeOffset peut effectuer une conversion implicite à partir de DateTime .
Le code suivant compile très 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!"}, }; } 

En effet, DateTimeOffset un opérateur de transtypage implicite défini:


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

Ce n'est pas du tout ce dont nous avons besoin. Nous voulions simplement que le programmeur pense, lors de l'écriture du code: "Mais dans quel fuseau horaire cet événement s'est-il produit?" Où obtenir la zone? " Souvent de domaines complètement différents, parfois de tables apparentées. Et puis faire une erreur sans réfléchir très facilement.


Maudites conversions implicites!


Tentative 2


Depuis que j'ai entendu parler un marteau analyseurs statiques , tout me semble clous cas appropriés pour eux. Nous devons écrire un analyseur statique qui interdit cette conversion implicite, et explique pourquoi ... Cela ressemble à beaucoup de travail. Quoi qu'il en soit, c'est le travail du compilateur de vérifier les types. Pour l'instant, mettez cette idée comme verbeuse.


Tentative 3


Maintenant, si nous voulions être dans le monde de F #, a déclaré Kirillkos .
Nous voudrions alors:


 type DateTimeOffsetStrict = Value of DateTimeOffset 

Et encore n'a pas imaginé improviser une sorte de magie nous sauverait. Dommage qu’ils n’écrivent pas F # dans notre bureau, et nous ne connaissons pas vraiment Kirillkos non plus :-)


Tentative 4


Impossible de faire quelque chose en C #? C'est possible, mais vous êtes tourmenté en vous convertissant d'avant en arrière. Arrêtez, mais nous venons de voir comment vous pouvez effectuer des conversions implicites!


 /// <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; } 

La chose la plus intéressante à propos de ce type est qu'il est implicitement converti dans les deux sens à partir de DateTimeOffset , mais une tentative de conversion implicite à partir de DateTime provoquera une erreur de compilation, les conversions à partir de DateTime ne sont possibles qu'explicitement. Le compilateur ne peut pas appeler la "chaîne" de conversions implicites, si elles sont définies dans notre code, la norme l'interdit ( citation de SO ). Autrement dit, cela fonctionne comme ceci:


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

mais pas comme ça:


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

Ce dont nous avions besoin!


Résumé


Nous ne savons pas encore si nous allons le mettre en œuvre. Seul tout le monde a été habitué à DateTimeOffset, et maintenant le remplacer par notre type est stupide. Oui, et à coup sûr, des problèmes surgiront au niveau de la liaison des paramètres EF, ASP.NET et à mille endroits. Mais la solution elle-même me semble intéressante. J'ai utilisé des astuces similaires pour surveiller la sécurité des entrées utilisateur - j'ai créé le type UnsafeHtml , qui est implicitement converti à partir d'une chaîne, mais vous ne pouvez le reconvertir en chaîne ou IHtmlString qu'en appelant IHtmlString .

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


All Articles