DateTimeOffset (streng)

Heute morgen ist mein Freund Kirillkos auf ein Problem gestoßen .


Problemcode


Hier ist sein Code:


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

Und dann viele, viele Implementierungen von IEventProvider , die Daten aus verschiedenen Tabellen und Datenbanken IEventProvider .


Problem : In all diesen Basen befindet sich alles in verschiedenen Zeitzonen. Dementsprechend ist beim Versuch, Ereignisse auf der Benutzeroberfläche auszugeben, alles furchtbar verwirrt.


Ehre sei Halesberg, wir haben Typen, mögen sie uns retten!


Versuch 1


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

DateTimeOffset großartiger Typ, der Offset-Informationen zu UTC speichert. Es wird perfekt von MS SQL und Entity Framework unterstützt (und in Version 6.3 wird es noch besser unterstützt ). In unserem Codestil ist dies für jeden neuen Code obligatorisch.


Jetzt können wir Informationen von denselben Anbietern sammeln und unter Verwendung von Typen konsistent alles auf der Benutzeroberfläche anzeigen. Sieg!


Problem : DateTimeOffset kann implizit von DateTime konvertieren.
Der folgende Code wird gut kompiliert:


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

Dies liegt daran, dass für DateTimeOffset ein impliziter Casting-Operator definiert ist:


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

Das brauchen wir überhaupt nicht. Wir wollten nur, dass der Programmierer beim Schreiben des Codes denkt: "Aber in welcher eigenen Zeitzone ist dieses Ereignis passiert?" Woher bekommt man die Zone? " Oft aus ganz anderen Bereichen, manchmal aus verwandten Tabellen. Und dann einen Fehler zu machen, ohne sehr leicht nachzudenken .


Verdammt implizite Konvertierungen!


Versuch 2


Da habe ich davon gehört ein Hammer statische Analysatoren , alles scheint mir Nägel geeignete Fälle für sie. Wir müssen einen statischen Analysator schreiben, der diese implizite Konvertierung verbietet und erklärt, warum ... Es sieht nach viel Arbeit aus. Auf jeden Fall ist es Aufgabe des Compilers, die Typen zu überprüfen. Setzen Sie diese Idee vorerst als ausführlich ein.


Versuch 3


Nun, wenn wir in der Welt von F # wären, sagte Kirillkos .
Wir würden dann:


 type DateTimeOffsetStrict = Value of DateTimeOffset 

Und weiter kam nicht mit Improvisation Eine Art Magie würde uns retten. Schade, dass sie in unserem Büro kein F # schreiben und wir Kirillkos auch nicht wirklich kennen :-)


Versuch 4


Kann in C # nichts getan werden? Es ist möglich, aber Sie werden gequält, indem Sie hin und her konvertieren. Hören Sie auf, aber wir haben gerade gesehen, wie Sie implizite Conversions durchführen können!


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

Das Interessanteste an diesem Typ ist, dass er implizit von DateTimeOffset hin und her DateTimeOffset wird. Ein Versuch, ihn implizit von DateTime zu konvertieren, DateTime jedoch zu einem Kompilierungsfehler. Konvertierungen von DateTime sind nur explizit möglich. Der Compiler kann die "Kette" impliziter Konvertierungen nicht aufrufen. Wenn sie in unserem Code definiert sind, ist dies durch den Standard verboten ( Zitat aus SO ). Das heißt, es funktioniert so:


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

aber nicht so:


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

Was wir brauchten!


Zusammenfassung


Wir wissen noch nicht, ob wir es umsetzen werden. Nur jeder war an DateTimeOffset gewöhnt, und jetzt ist es dumm, es durch unseren Typ zu ersetzen. Ja, und auf der Ebene der EF-, ASP.NET-Parameterbindung und an tausend Stellen treten mit Sicherheit Probleme auf. Aber die Lösung selbst scheint mir interessant zu sein. Ich habe ähnliche Tricks verwendet, um die Sicherheit von Benutzereingaben zu überwachen. Ich habe den Typ UnsafeHtml , der implizit aus einer Zeichenfolge konvertiert wird. Sie können ihn jedoch nur durch Aufrufen von IHtmlString in eine Zeichenfolge oder einen IHtmlString .

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


All Articles