2010-06-25 12 views
6

Ich stelle mir die Fähigkeit vor, fließenden Code zu schreiben, der Zahlen innerhalb von Codebasen Bedeutung hinzufügt. Angenommen, Sie möchten eine Zahl, die eine Entfernung in Meilen darstellt. Sie müssten so etwas wie:Erweiterungsmethode für Int32 in C#

Nutzungs:

var result = myMethod(100.Miles()); 

Ich denke, das wäre viel besser lesbar als einfach in der int vorbei, und man könnte vermutlich die Überprüfung der Grenzen zu den Miles-Typ gelten.

Erweiterung Methode und Struktur Implementierung:

static class IntExtensions 
{ 
    public static Miles(this int i) { get { return new Miles { Count = i }; } } 
} 

public struct Miles 
{ 
    public int Count { get; private set; } //optionally perform bounds checking 
} 

Wäre eine solche Idee nützlich sein, oder ist es zu spät an einem heißen Freitag?

Edit: Ja, sieht nicht ganz so gut ohne Extension-Eigenschaften ... Entschuldigung für den überstürzten ungültigen Code. Es war nur eine Idee.

+3

Ihre Erweiterungsmethode und ihre Klassendefinition ist falsch. –

+1

Wie stellen Sie sich das vor? Hinzufügen von Sachen für alle Arten von Einheiten? 100.Miles() * 2.43.Kilogramme()/454.MoonCycles()? – simendsjo

+0

Sie meinen "vorstellen" nicht "vorstellen", und während es für Ihre einzigartige Implementierung großartig sein kann, werde ich nichts davon in meiner finanziellen Anwendung haben. Ich will .Dollar und .Euros. – StingyJack

Antwort

4

Es ist eine interessante Idee, aber ich würde für einen wirklich starken Anwendungsfall bitten, bevor Sie so etwas tun. Zum einen, wenn eine Zahl als "Meile" gecastet wird, können Sie sie nicht mehr als int behandeln. Sie müssen entweder die gesamte Skala der Operatoren implementieren oder die Meilen zurück auf Ganzzahlen setzen, bevor Sie sie arithmetisch ausführen. Es ist viel zusätzliche Arbeit, wenn es keinen guten Grund dafür gibt.

Es gibt einige Fälle, in denen dies eine gute Strategie wäre. Ich denke, ich erinnere mich an eine Multi-Millionen-Dollar-Rakete oder etwas, das NASA einmal gescheitert lost a $125 million space ship hörte, weil Programmierer die falschen Maßeinheiten in eine Funktion übergeben. Dies würde Ihnen helfen, dieses Problem zu vermeiden.

Zu einem verwandten Hinweis könnten Sie F # interessiert sein, die built-in support for units of measure hat.

2

Ihre Miles Struktur sollte unveränderlich sein.

es ändern zu

public struct Miles { 
    public Miles(int count) : this() { Count = count; } //optionally perform bounds checking 

    public int Count { get; private set; } 
} 
2

Ein Kommentar: Was ist der Sinn macht Miles wandelbar? Ein int ist nicht änderbar, warum es veränderlich machen, sobald es eine Einheit hat?

(Additionaly haben Erweiterungseigenschaften in C# 4 eingeführt werden? Sonst wird dies nicht funktionieren.)

Schließlich, wenn Sie Einheiten hinzufügen möchten, sollten sie zusammensetzbare gemacht werden, und ich zur Zeit nicht sehen wie man das erreicht.

Zum Beispiel sollte der folgende Code kompilieren:

var x = 100.km; 
var y = 10.sec; 
var kmh = x/y; // What type does kmh have? 

In C++ gibt es eine Bibliothek, die diese implementiert durch die Typen als Tupel der Dimensionen aller sieben grundlegenden physikalischen Einheiten darstellt, aber dies nicht arbeite in C#, da es Integer als Template-Argumente benötigt.

0

Persönlich sehe ich keinen Punkt.

Ich sehe keinen Grund, die Unterschrift von myMethod sollte nicht sein:

public object MyMethod(int miles) 
{ 
    // bounds checking on int here 
    // then logic 
} 

Sie könnten auch Code Verträge verwenden, um die Dinge noch deutlicher zu machen.

Das Hinzufügen eines Aufrufs zu .Miles() und das Erstellen der int Mutable ist verwirrender.

+2

"Sie können Code Contracts auch verwenden, um die Dinge noch expliziter zu machen." Die Verwendung von Typen anstelle von Contracts ist jedoch noch expliziter und bietet eine bessere Compiler-Unterstützung. Wenn Sie das Typsystem als ein Proof-System betrachten, mit dem Sie Behauptungen und Invarianten über Ihren Code formulieren können (und das ist wirklich die * Definition * eines Typsystems), dann ist das Hinzufügen von Einheiten sehr sinnvoll. Abgesehen davon, dass Code-Verträge auf der Callee-Site mehr Code-Duplizierung erfordern. –

+0

@Konrad Rudolph - Als ich Code Contracts vorschlug, bezog ich mich auf die Grenzen, die Logik und Validierung überprüfen. Die Verwendung von Code-Contracts würde es ihm ermöglichen, den Bereich der erwarteten Werte besser zu definieren und es dem Compiler zu ermöglichen, die Entwickler zu warnen, wenn ihre Werte außerhalb des Bereichs liegen. Ich stimme zu, dass ein Typ expliziter ist, aber in diesem Fall ist es ein einfacher Wrapper um Int32, der meiner Meinung nach keinen Mehrwert liefert. –

+0

"es ist ein einfacher Wrapper ... der keinen Mehrwert hinzufügt" verrät ein Missverständnis, für welche Typen es da ist. Typen müssen keine Daten bündeln oder viele Aktionen hinzufügen. Die bloße Tatsache, dass * verschiedene * Typen existieren, ist ein wichtiger Zweck von Typen. Warum gibt es sonst noch andere physikalische Einheiten? Jeder Physiker wird Ihnen sagen, dass wenn Sie eine Berechnung durchführen (auf Papier oder im Computer) und die Einheiten am Ende übereinstimmen, die Berechnung höchstwahrscheinlich auch richtig ist. Das gleiche Argument gilt für das Überprüfen von Fakten (= Kompilieren mit starken Typen). Bedenken Sie: Warum gibt es die Klasse "TimeSpan" noch? –

1

So sollte Ihr Design aussehen.

Beachten Sie, dass wir noch keine Erweiterungseigenschaften in C# haben, es sind nur Erweiterungsmethoden.

class Program 
{ 
    static void Main(string[] args) 
    { 
     var result = myMethod(100.ToMiles()); 
     //Miles miles = 100.ToMiles(); 
    }   
} 

static class IntExtensions 
{ 
    public static Miles ToMiles(this int miles) 
    { 
     return new Miles(miles); 
    } 
} 

struct Miles 
{ 
    public int Count { get; private set; } 

    public Miles(int count) 
     : this() 
    { 
     if (count < 0) 
     { 
      throw new ArgumentException("miles type cannot hold negative values."); 
     } 
     this.Count = count; 
    } 
} 
+1

Warum das redundante Präfix 'To'? '100.Miles()' wird vollkommen klar und eindeutig sein. –

+1

Die Methode sollte einen Namen angeben, der seinen Zweck angibt. ToMiles() übermittelt die Nachricht, dass diese Methode "int" in Miles konvertiert. Genau wie jeder .NET-Typ konvertiert int.ToString() den int-Wert in seine Zeichenfolgendarstellung. –

+1

Ich stimme dem '.ToMiles()' Konzept zu. Es entspricht anderen Methoden wie 'ToString()' oder den verschiedenen Methoden von 'Convert'. –

4

sollten Sie haben nicht magic numbers in Ihrem Code, for good reason und Verfahren eine Erweiterung Schreiben nicht viel tun, um das Problem zu lindern.Du hast immer noch eine magische Zahl im Umlauf.

Wenn es konstant ist, machen Sie es zu einer Konstante und schließen Sie _ _ MILES _ in den Namen der Konstanten ein.

Auch, warum nicht den Wert in eine Klasse oder Struktur namens Abstand, die einfach einen numerischen Wert und eine Enumeration, die die Maßeinheit angibt?

Etwas wie:

public class Distance { 
    private double _distanceValue; 
    private UnitOfMeasure _uom; 

    public double DistanceValue { 
     get { return _distanceValue; } 
     set { _distanceValue = value; } 
    } 

     public UnitOfMeasure Uom { 
     get { return _uom; } 
     set { _uom = value; } 
    } 
} 

public enum UnitOfMeasure { 
    Kilometers, 
    Miles, 
    Feet, 
    Inches, 
    Parsecs 
} 
+1

Obwohl ich kein Fan von magischen Zahlen bin, denke ich, dass es einen Fall gibt, wo dies sinnvoll ist, und das ist, wenn dieses Muster verwendet wird, um eine Strategie in einem Strategie-Muster zu erstellen.In der Domäne, in der ich es verwende, ist es wichtig, schnell neue Strategien im Code zu erstellen und sie in das Prozess-Framework zu integrieren. Da die Strategie Domänenlogik verkörpert, liegt es nahe, den Wert anzugeben, der für die Implementierung der Strategie im Code erforderlich ist. Dieser Ansatz macht es sehr einfach zu schreiben und vor allem lesen. – codekaizen

+0

Das liest sich ziemlich gut zu mir: var result = myMethod (neuer Abstand (100, UnitOfMeasure.Miles)); Plus, stellen Sie sich vor, wenn Ihre Distance-Klasse eine ConvertTo (UnitOfMeasure) -Methode hatte, und noch besser, sie wurde von einer Basisklasse diktiert und von den Klassen Mass, Weight usw. geerbt. –

+1

"Warum nicht den Wert in eine Klasse oder Struktur namens Distance ... einbinden", weil dann jede Kompilierzeitüberprüfung verloren geht. –

0
public static class Int32Extensions 
{ 
    public static Miles ToMiles(this Int32 distance) 
    { 
     return new Miles(distance); 
    } 
} 

public class Miles 
{ 
    private Int32 _distance; 

    public Miles(Int32 distance) 
    { 
     _distance = distance; 
    } 

    public Int32 Distance 
    { 
     get 
     { 
      return _distance; 
     } 
    } 
} 
2

Ich benutze diese Idee eine Art interner Grammatik für ein Projekt, das mit physikalischen Maßnahmen behandelt zu erstellen. Ich war anfangs zweifelhaft, aber ich mag es jetzt wirklich, weil es den Quellcode sehr lesbar und leicht macht und Spaß macht zu schreiben. Hier ein Beispiel:

A Gerätetyp:

public struct Celsius : IEquatable<Celsius> 
{ 
    private readonly Double _value; 
    public const string Abbreviation = "°C"; 

    public Celsius(Double value) 
    { 
     _value = value; 
    } 

    public Boolean Equals(Celsius other) 
    { 
     return _value == other._value; 
    } 

    public override Boolean Equals(Object other) 
    { 
     if (!(other is Celsius)) 
     { 
      return false; 
     } 

     return Equals((Celsius)other); 
    } 

    public override int GetHashCode() 
    { 
     return _value.GetHashCode(); 
    } 

    public override string ToString() 
    { 
     return _value + Abbreviation; 
    } 

    public static explicit operator Celsius(Double value) 
    { 
     return new Celsius(value); 
    } 

    public static explicit operator Double(Celsius value) 
    { 
     return value._value; 
    } 

    public static Boolean operator >(Celsius l, Celsius r) 
    { 
     return l._value > r._value; 
    } 

    public static bool operator <(Celsius l, Celsius r) 
    { 
     return l._value < r._value; 
    } 

    public static Boolean operator >=(Celsius l, Celsius r) 
    { 
     return l._value >= r._value; 
    } 

    public static bool operator <=(Celsius l, Celsius r) 
    { 
     return l._value <= r._value; 
    } 

    public static Boolean operator ==(Celsius l, Celsius r) 
    { 
     return l._value == r._value; 
    } 

    public static bool operator !=(Celsius l, Celsius r) 
    { 
     return l._value != r._value; 
    } 
} 

Einheit Extensions Klasse:

public static class UnitsExtensions 
{ 

    public static Celsius Celsius(this Double value) 
    { 
     return new Celsius(value); 
    } 

    public static Celsius Celsius(this Single value) 
    { 
     return new Celsius(value); 
    } 

    public static Celsius Celsius(this Int32 value) 
    { 
     return new Celsius(value); 
    } 

    public static Celsius Celsius(this Decimal value) 
    { 
     return new Celsius((Double)value); 
    } 

    public static Celsius? Celsius(this Decimal? value) 
    { 
     return value == null ? default(Celsius?) : new Celsius((Double)value); 
    } 
} 

Verbrauch:

var temp = (Celsius)value; 

if (temp <= 0.Celsius()) 
{ 
    Console.Writeline("It's cold!"); 
} 
else if (temp < 20.Celsius()) 
{ 
    Console.Writeline("Chilly..."); 
} 
else if (temp < 30.Celsius()) 
{ 
    Console.Writeline("It's quite lovely"); 
} 
else 
{ 
    Console.Writeline("It's hot!"); 
} 

habe ich eine Reihe dieser Arten für verschiedene Maßnahmen haben, wie Millimeter, Radians, Degrees, MillimetersPerSecond usw. ich sogar so weit Abteilung zu implementieren gegangen sind, so dass, wenn ich teilen, sagen wir, MillimetersPerSecond von Millimeters, erhalte ich eine TimeSpan Wert im Gegenzug. Vielleicht ist dies über Bord, aber ich habe die Art der Sicherheit und die geistige Leichtigkeit der Verwendung der Typen gefunden, die es wert sind, sie zu implementieren und zu pflegen.

+4

Du lebst sicher nicht in Norwegen :) 0 == "Es ist ziemlich schön", 20 == "Es ist heiß!", 30 == "Es ist unerträglich!",> 30 == "Ja richtig!" – simendsjo

+2

Ich verstehe, dass es sehr nützlich sein kann, wenn man mit den gleichen "Einheiten" arbeitet; 10.Celsius() - 10.Fahrenheit(), aber es wäre schön, Einheiten wie 10.Km()/2.Seconds() zu mischen und 5 km/s zu bekommen – simendsjo

+1

@simendsjo - ich habe das getan mit den MillimeternPerSekund/Millimetern => TimeSpan. Es funktioniert ziemlich gut. Es würde besser funktionieren, wenn ich TimeSpan einen "Erweiterungsoperator" zuweisen könnte, um Millimetern/TimeSpan = MillimetersPerSecond zu erlauben. – codekaizen

1

Ich griff dies (mit sehr kleinen Tweaks) aus einer früheren SO Frage. Ich bevorzuge diesen Stil, da er den gängigen Ansätzen wie DateTime und TimeSpan entspricht.

[StructLayout(LayoutKind.Sequential), ComVisible(true)] 
    public struct Distance : IEquatable<Distance>, IComparable<Distance> 
    { 
     private const double MetersPerKilometer = 1000.0; 
     private const double CentimetersPerMeter = 100.0; 
     private const double CentimetersPerInch = 2.54; 
     private const double InchesPerFoot = 12.0; 
     private const double FeetPerYard = 3.0; 
     private const double FeetPerMile = 5280.0; 
     private const double FeetPerMeter = CentimetersPerMeter/(CentimetersPerInch * InchesPerFoot); 
     private const double InchesPerMeter = CentimetersPerMeter/CentimetersPerInch; 

     public static readonly Distance Zero = new Distance(0.0); 

     private readonly double meters; 

     /// <summary> 
     /// Initializes a new Distance to the specified number of meters. 
     /// </summary> 
     /// <param name="meters"></param> 
     public Distance(double meters) 
     { 
      this.meters = meters; 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional kilometers. 
     /// </summary> 
     public double TotalKilometers 
     { 
      get 
      { 
       return meters/MetersPerKilometer; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional meters. 
     /// </summary> 
     public double TotalMeters 
     { 
      get 
      { 
       return meters; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional centimeters. 
     /// </summary> 
     public double TotalCentimeters 
     { 
      get 
      { 
       return meters * CentimetersPerMeter; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional yards. 
     /// </summary> 
     public double TotalYards 
     { 
      get 
      { 
       return meters * FeetPerMeter/FeetPerYard; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional feet. 
     /// </summary> 
     public double TotalFeet 
     { 
      get 
      { 
       return meters * FeetPerMeter; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional inches. 
     /// </summary> 
     public double TotalInches 
     { 
      get 
      { 
       return meters * InchesPerMeter; 
      } 
     } 

     /// <summary> 
     /// Gets the value of the current Distance structure expressed in whole and fractional miles. 
     /// </summary> 
     public double TotalMiles 
     { 
      get 
      { 
       return meters * FeetPerMeter/FeetPerMile; 
      } 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of kilometers. 
     /// </summary> 
     /// <param name="value">A number of kilometers.</param> 
     /// <returns></returns> 
     public static Distance FromKilometers(double value) 
     { 
      return new Distance(value * MetersPerKilometer); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of meters. 
     /// </summary> 
     /// <param name="value">A number of meters.</param> 
     /// <returns></returns> 
     public static Distance FromMeters(double value) 
     { 
      return new Distance(value); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of centimeters. 
     /// </summary> 
     /// <param name="value">A number of centimeters.</param> 
     /// <returns></returns> 
     public static Distance FromCentimeters(double value) 
     { 
      return new Distance(value/CentimetersPerMeter); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of yards. 
     /// </summary> 
     /// <param name="value">A number of yards.</param> 
     /// <returns></returns> 
     public static Distance FromYards(double value) 
     { 
      return new Distance(value * FeetPerYard/FeetPerMeter); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of feet. 
     /// </summary> 
     /// <param name="value">A number of feet.</param> 
     /// <returns></returns> 
     public static Distance FromFeet(double value) 
     { 
      return new Distance(value/FeetPerMeter); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of inches. 
     /// </summary> 
     /// <param name="value">A number of inches.</param> 
     /// <returns></returns> 
     public static Distance FromInches(double value) 
     { 
      return new Distance(value/InchesPerMeter); 
     } 

     /// <summary> 
     /// Returns a Distance that represents a specified number of miles. 
     /// </summary> 
     /// <param name="value">A number of miles.</param> 
     /// <returns></returns> 
     public static Distance FromMiles(double value) 
     { 
      return new Distance(value * FeetPerMile/FeetPerMeter); 
     } 

     public static bool operator ==(Distance a, Distance b) 
     { 
      return (a.meters == b.meters); 
     } 

     public static bool operator !=(Distance a, Distance b) 
     { 
      return (a.meters != b.meters); 
     } 

     public static bool operator >(Distance a, Distance b) 
     { 
      return (a.meters > b.meters); 
     } 

     public static bool operator >=(Distance a, Distance b) 
     { 
      return (a.meters >= b.meters); 
     } 

     public static bool operator <(Distance a, Distance b) 
     { 
      return (a.meters < b.meters); 
     } 

     public static bool operator <=(Distance a, Distance b) 
     { 
      return (a.meters <= b.meters); 
     } 

     public static Distance operator +(Distance a, Distance b) 
     { 
      return new Distance(a.meters + b.meters); 
     } 

     public static Distance operator -(Distance a, Distance b) 
     { 
      return new Distance(a.meters - b.meters); 
     } 

     public static Distance operator -(Distance a) 
     { 
      return new Distance(-a.meters); 
     } 

     public override bool Equals(object obj) 
     { 
      if (!(obj is Distance)) 
       return false; 

      return Equals((Distance)obj); 
     } 

     public bool Equals(Distance value) 
     { 
      return this.meters == value.meters; 
     } 

     public int CompareTo(Distance value) 
     { 
      return this.meters.CompareTo(value.meters); 
     } 

     public override int GetHashCode() 
     { 
      return meters.GetHashCode(); 
     } 

     public override string ToString() 
     { 
      return string.Format("{0} meters", TotalMeters); 
     } 
    } 
Verwandte Themen