Muster für das Erstellen eines Einfachen und Effizienten Wert-Typ
Motivation:
In der Lesung von Mark Seemann blog auf Code Smell: Automatische Eigenschaft sagt er gegen Ende:
Die Quintessenz ist, dass die automatische Eigenschaften sind selten angemessen.
In der Tat, Sie sind nur geeignet, wenn der Typ der Eigenschaft ist ein
Wert geben und alle erdenklichen Werte erlaubt sind.
Er gibt int Temperature
als ein Beispiel, einen schlechten Geruch und schlägt die beste fix ist, Gerät bestimmten Wert geben wie Celsius. Also habe ich beschlossen, zu versuchen, schreiben Sie eine benutzerdefinierte Celsius-Wert geben, kapselt alle bounds checking und Typ-Konvertierung Logik als eine übung in mehr SOLID.
Grundlegende Anforderungen:
- Unmöglich, einen ungültigen Wert
- Kapselt die Konvertierung Operationen
- Effient Bewältigung (äquivalent zu int its austauschen)
- So intuitiv zu benutzen wie möglich (versuche, die für die Semantik eines int)
Umsetzung:
[System.Diagnostics.DebuggerDisplay("{m_value}")]
public struct Celsius //: IComparable, IFormattable, etc...
{
private int m_value;
public static readonly Celsius MinValue = new Celsius() { m_value = -273 }; //absolute zero
public static readonly Celsius MaxValue = new Celsius() { m_value = int.MaxValue };
private Celsius(int temp)
{
if (temp < Celsius.MinValue)
throw new ArgumentOutOfRangeException("temp", "Value cannot be less then Celsius.MinValue (absolute zero)");
if (temp > Celsius.MaxValue)
throw new ArgumentOutOfRangeException("temp", "Value cannot be more then Celsius.MaxValue");
m_value = temp;
}
public static implicit operator Celsius(int temp)
{
return new Celsius(temp);
}
public static implicit operator int(Celsius c)
{
return c.m_value;
}
//operators for other numeric types...
public override string ToString()
{
return m_value.ToString();
}
//override Equals, HashCode, etc...
}
Tests:
[TestClass]
public class TestCelsius
{
[TestMethod]
public void QuickTest()
{
Celsius c = 41;
Celsius c2 = c;
int temp = c2;
Assert.AreEqual(41, temp);
Assert.AreEqual("41", c.ToString());
}
[TestMethod]
public void OutOfRangeTest()
{
try
{
Celsius c = -300;
Assert.Fail("Should not be able to assign -300");
}
catch (ArgumentOutOfRangeException)
{
//pass
}
catch (Exception)
{
Assert.Fail("Threw wrong exception");
}
}
}
Fragen:
- Ist es ein Weg, um MinValue/MaxValue const statt readonly? Blick auf die BCL mag ich, wie die meta-Daten, die definition von int klar MaxValue und MinValue als compile-Zeit-Konstanten. Wie kann ich imitieren? Ich sehe nicht ein Weg, um eine Celsius-Objekt ohne Aufruf des Konstruktors oder aussetzen der Implementierung detail, Celsius speichert einen int.
- Bin ich, fehlt jede usability-features?
- Gibt es ein besseres Muster für die Erstellung einer benutzerdefinierten einzigen Feld Wert geben?
- Überprüfen Sie heraus diese Frage (somewnat beantworten Sie "fehlende usability-features" - Teil) - stackoverflow.com/questions/441309/why-are-mutable-structs-evil und links aus Ihr heraus. Nützlich für alle value-Typen.
- +1 für die Frage, auf immer SOLIDE.
- Ich habe gelesen, alle die "mutable structs sind böse" - posts vor. Ich bin damit einverstanden. Das problem ist, dass wenn ich das private Feld readonly dann gebrannt.MaxValue ruft der Konstruktor erfordert Celsius.MaxValue-bereits definiert. Diese ist kreisförmig und führt zu einer runtime exception. Das ist, warum ich bin mit einem Standard-Konstruktor in der MaxValue-definition. Kennst du eine Möglichkeit, um dieses? Einen besonderen Zweck "nicht überprüfen bounds" privaten Konstruktor fühlt sich falsch an.
- Ich wusste nicht, dass. Ich denke, dass spezielle Methode (private CreateConstantValue()?) das schafft Konstanten, die für den gegebenen Typ wäre nützlich für die selbst-Dokumentation der code - den code anzuschauen, wie es jetzt ist gibt es keine Möglichkeit zu wissen, warum Sie zu rufen Standard-Konstruktor.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Nicht. Aber die BCL nicht tun, entweder. Zum Beispiel, DateTime.MinValue ist
static readonly
. Ihre aktuelle Ansatz, fürMinValue
undMaxValue
geeignet ist.Als für Ihre beiden anderen Fragen - usability und das Muster selbst.
Persönlich würde ich vermeiden, die automatische Konvertierungen (implizite Konvertierung Operatoren) für eine "Temperatur" - Typ wie dieser. Eine Temperatur, die nicht einen ganzzahligen Wert (in der Tat, wenn Sie wurden gehen, um dies zu tun, würde ich behaupten, dass es sein sollte-floating-point - 93.2 ° C ist vollkommen gültig.) Die Behandlung einer Temperatur als integer und vor allem die Behandlung von integer-Wert implizit als eine Temperatur scheint unangemessen und eine mögliche Ursache für Fehler.
Ich finde, dass Strukturen mit impliziter Konvertierung verursachen oft mehr usability-Probleme als die, die Sie ansprechen. Einen Benutzer zwingen, zu schreiben:
Ist nicht wirklich viel schwieriger ist als die implizite Konvertierung von einem integer. Es ist weit mehr jedoch klar.
int
in diesem Fall.Ich denke aus usability-Sicht würde ich entscheiden sich für eine Art
Temperature
eher alsCelsius
.Celsius
ist nur eine Maßeinheit, während einTemperature
darstellen würde, eine tatsächliche Messung. Dann ist dein Typ könnte Unterstützung für mehrere Einheiten, wie Celsius, Fahrenheit und Kelvin. Ich würde mich auch entscheiden für dezimal-als backing-Speicher.Etwas entlang diesen Linien:
Ich würde vermeiden, dass die implizite Konvertierung als Reed-Staaten, es macht die Dinge weniger offensichtlich. Allerdings würde ich die Operatoren überladen (<, >, ==, +, -, *, /) in diesem Fall würde es Sinn machen, die Durchführung dieser Art von Operationen. Und wer weiß, in einer zukünftigen version .net, dass wir sogar in der Lage sein anzugeben Betreiber Einschränkungen und schließlich in der Lage sein, mehr zu schreiben, wiederverwendbaren Datenstrukturen (stellen Sie sich eine Statistik, in der Klasse, die können berechnen Sie Statistiken für jede Art unterstützt +, -, *, /).
Temperature.ToString("C")
).DebuggerDisplay
ist nützlich touch. Ich möchte hinzufügen Einheit der Messungen "{m_value} C", so können Sie sofort sehen, den Typ.Je nach Ziel-Verwendung können Sie auch haben wollen generischen conversion-Rahmen zu/von der Basis-Einheiten in addtion zu konkreten Klassen. I. e. speichern der Werte in SI-Einheiten, doch in der Lage sein, um anzeigen/Bearbeiten, basierend auf Kultur, wie (°C, km, kg) vs. (Grad F, mi, lb).
Können Sie auch check out F# Maßeinheiten für additioanl Ideen ( http://msdn.microsoft.com/en-us/library/dd233243.aspx ) - beachten Sie, dass es compile-Zeit-Konstrukt.
Ich denke, dies ist ein völlig in Ordnung, die Umsetzung Muster für value-Typen. Ich habe getan, ähnliche Dinge in der Vergangenheit, haben gut geklappt.
Nur eins, da
Celsius
implizit konvertierbar/ausint
sowieso, legen Sie die Grenzen so:Jedoch in der Realität es gibt keinen praktischen Unterschied zwischen
static readonly
undconst
.