Wie erstelle ich eine generische Konverter für Maßeinheiten in C#?
Habe ich versucht zu lernen, ein wenig mehr über Delegaten und Lambda-Ausdrücke während der Arbeit auf einer kleinen cooking-Projekt beinhaltet, dass die Temperatur-Konvertierung wie auch einige Koch-Messung Konvertierungen, z.B. Metrische und ich habe versucht, zu denken, ein Weg, um eine erweiterbare Einheit-Konverter.
Hier ist, was ich mit gestartet, zusammen mit der code-Kommentare auf, was meine Pläne waren. Ich habe keinen plan, es zu benutzen, wie die unten, ich war einfach nur testen, einige features von C# weiß ich nicht sehr gut, ich bin auch unsicher, wie nehmen Sie diese weiter. Hat jemand irgendwelche Vorschläge auf, wie Sie schaffen, was ich spreche, in den Kommentaren unten? Dank
namespace TemperatureConverter
{
class Program
{
static void Main(string[] args)
{
//Fahrenheit to Celsius : [°C] = ([°F] − 32) × 5⁄9
var CelsiusResult = Converter.Convert(11M,Converter.FahrenheitToCelsius);
//Celsius to Fahrenheit : [°F] = [°C] × 9⁄5 + 32
var FahrenheitResult = Converter.Convert(11M, Converter.CelsiusToFahrenheit);
Console.WriteLine("Fahrenheit to Celsius : " + CelsiusResult);
Console.WriteLine("Celsius to Fahrenheit : " + FahrenheitResult);
Console.ReadLine();
//If I wanted to add another unit of temperature i.e. Kelvin
//then I would need calculations for Kelvin to Celsius, Celsius to Kelvin, Kelvin to Fahrenheit, Fahrenheit to Kelvin
//Celsius to Kelvin : [K] = [°C] + 273.15
//Kelvin to Celsius : [°C] = [K] − 273.15
//Fahrenheit to Kelvin : [K] = ([°F] + 459.67) × 5⁄9
//Kelvin to Fahrenheit : [°F] = [K] × 9⁄5 − 459.67
//The plan is to have the converters with a single purpose to convert to
//one particular unit type e.g. Celsius and create separate unit converters
//that contain a list of calculations that take one specified unit type and then convert to their particular unit type, in this example its Celsius.
}
}
//at the moment this is a static class but I am looking to turn this into an interface or abstract class
//so that whatever implements this interface would be supplied with a list of generic deligate conversions
//that it can invoke and you can extend by adding more when required.
public static class Converter
{
public static Func<decimal, decimal> CelsiusToFahrenheit = x => (x * (9M / 5M)) + 32M;
public static Func<decimal, decimal> FahrenheitToCelsius = x => (x - 32M) * (5M / 9M);
public static decimal Convert(decimal valueToConvert, Func<decimal, decimal> conversion) {
return conversion.Invoke(valueToConvert);
}
}
}
Update: Versuchen zu klären, meine Frage:
Verwendung nur meine Temperatur Beispiel unten, wie würde ich eine Klasse erstellen, die enthält eine Liste der lambda-Konversionen zu Celsius, die Sie dann weitergeben einer bestimmten Temperatur und es wird versuchen, und konvertieren, dass zu Celsius (wenn die Berechnung vorhanden ist)
Beispiel pseudo-code:
enum Temperature
{
Celcius,
Fahrenheit,
Kelvin
}
UnitConverter CelsiusConverter = new UnitConverter(Temperature.Celsius);
CelsiusConverter.AddCalc("FahrenheitToCelsius", lambda here);
CelsiusConverter.Convert(Temperature.Fahrenheit, 11);
- Mengeneinheiten werden unterstützt in F# und ich denke, es kann eine gute Funktion für C# vNext. Für jetzt habe ich dieses Projekt gefunden QuantityTypes, die Maßeinheiten in C#.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich dachte, das war ein Interessantes kleines problem, also habe ich beschlossen, um zu sehen, wie schön dies sein könnte, eingewickelt in eine generische Implementierung. Das ist nicht gut getestet (und funktioniert nicht mit allen fehlerfällen - wie wenn Sie nicht registrieren, die Umrechnung für ein bestimmtes Gerät geben, dann pass auf), aber es könnte nützlich sein. Der Fokus war auf die geerbten Klasse (
TemperatureConverter
) so aufgeräumt wie möglich.Den generischen Typ args sind für eine enum repräsentiert die Einheiten, und der Typ des Wertes. Um es zu verwenden, Sie müssen nur Erben von dieser Klasse (die Typen) und registrieren einige lambdas, um die Konvertierung zu tun. Hier ist ein Beispiel für Temperatur (mit einigen dummy-Berechnungen):
Und dann ist es ziemlich einfach:
Haben Sie einen guten start, aber wie Jon schon sagte, es ist derzeit nicht Typ-sicher; der Konverter hat keine Fehler-überprüfung zur Sicherstellung, dass die dezimale wird es ist ein Celsius-Wert.
So, an dieser weiter, würde ich anfangen, die Einführung von struct-Typen, nehmen Sie den numerischen Wert ein, und wenden Sie es auf eine Mengeneinheit. In den Mustern der Enterprise-Architektur (auch bekannt als die Gang of Four-design Pattern), das nennt man das "Geld" - Muster, nach der häufigsten Verwendung, zu bezeichnen, dass eine Menge von einer Art von Währung. Das Muster gilt für alle numerischen Betrag, der benötigt eine Maßeinheit sinnvoll sein.
Beispiel:
Nun, haben Sie eine einfache Art, die Sie verwenden können, um zu erzwingen, dass die Umsätze, die Sie machen, nehmen Sie eine Temperatur, dass ist der richtige Maßstab, und ein Ergebnis zurückgeben Temperatur bekannt sein, in die andere Waagschale.
Ihre Funcs müssen eine zusätzliche Zeile, um zu überprüfen, dass die Eingabe entspricht den Ausgang; Sie können weiterhin verwenden, lambdas, oder Sie können es nehmen einen Schritt weiter mit einer einfachen Strategie-Muster:
Weil diese Arten von Konvertierungen sind zwei-Wege, können Sie erwägen die Einrichtung einer Schnittstelle zum Griff in beide Richtungen, mit einem "ConvertBack" - Methode oder ähnliches, der eine Temperatur in der Celsius-Skala und konvertieren Fahrenheit. Das reduziert Ihre Klasse zählen. Dann, anstelle der class-Instanzen, Ihre Wörterbuch-Werte könnte sein, Verweise auf Methoden, die auf Instanzen der Wandler. Dies erhöht die Komplexität etwas von der Einrichtung der wichtigsten TemperatureConverter Strategie-picker, aber reduziert die Anzahl der conversion-Strategie-Klassen, die Sie definieren müssen.
Beachten Sie auch, dass die Fehlerüberprüfung erfolgt zur Laufzeit, wenn Sie tatsächlich versuchen, die Konvertierung zu machen, dass dies der code gründlich getestet in allen Verwendungen zu gewährleisten, ist es immer richtig. Um dies zu vermeiden, können Sie die Ableitung der Basis-Temperatur-Klasse zu produzieren CelsiusTemperature und FahrenheitTemperature Strukturen, die würden einfach definieren Sie Ihre Waage als ein konstanter Wert. Dann, die ITemperatureConverter gemacht werden könnten, Generika zu zwei Typen, beide Temperaturen ab, so dass Sie compile-Zeit überprüft haben, dass Sie angeben, die Umwandlung, die Sie denken, Sie sind. die TemperatureConverter können auch gemacht werden, um dynamisch zu finden ITemperatureConverters, bestimmen die Arten, die Sie konvertieren zwischen, und automatisch einrichten das Wörterbuch der Wandler, so dass Sie nie sorgen zu machen über neue hinzufügen. Dies geht allerdings auf Kosten der erhöhten Temperatur-Klasse zählen; Sie müssen vier domain-Klassen (Basis-und drei abgeleitete Klassen) statt. Es wird auch langsam die Schaffung eines TemperatureConverter Klasse wie der Quelltext zu bauen nachdenklich den Konverter Wörterbuch wird ganz ein bisschen nachdenken.
Könnte man auch ändern, in der Enumerationen für die Maßeinheiten zu werden "marker-Klassen"; leere Klassen, die haben keine andere Bedeutung als dass Sie von dieser Klasse ableiten von anderen Klassen. Man könnte dann definieren Sie eine vollständige Hierarchie von "UnitOfMeasure" Klassen repräsentieren die verschiedenen Maßeinheiten und können verwendet werden als generische Typ die Argumente und Zwänge; ITemperatureConverter könnte allgemein auf zwei Arten, beide sind gezwungen zu werden TemperatureScale Klassen, und eine CelsiusFahrenheitConverter Umsetzung würde in der Nähe der generischen Schnittstelle zu den Arten CelsiusDegrees und FahrenheitDegrees beide abgeleitet von TemperatureScale. Das ermöglicht Ihnen setzen die Maßeinheiten aus, die sich als Einschränkungen der Konvertierung, wodurch wiederum Umrechnungen zwischen verschiedenen Maßeinheiten (bestimmten Einheiten bestimmte Materialien haben bekannt Umbauten; 1 britische Imperial Pint Wasser wiegt 1.25 kg).
Alle diese design-Entscheidungen vereinfachen wird eine Art von Veränderung, um das design, aber einige Kosten (entweder machen Sie sonst etwas schwerer tun oder fallender Algorithmus-performance). Es ist bis zu Sie zu entscheiden, was ist wirklich "easy" für Sie, in den Kontext der gesamten Anwendung und Codierung-Umgebung arbeiten Sie in.
EDIT: Die Nutzung Sie wollen, von Ihr zu Bearbeiten, ist extrem einfach für die Temperatur. Allerdings, wenn Sie möchten, eine generische UnitConverter, dass funktioniert mit jedem UnitofMeasure, dann Sie nicht mehr wollen, Enums und bilden Ihre Einheiten Messen, da Enums nicht haben, können Sie eine benutzerdefinierte Hierarchie-Vererbung (Sie stammen direkt aus dem System.Enum).
Können Sie angeben, dass der Standard-Konstruktor akzeptiert alle Enum, aber dann haben Sie, um sicherzustellen, dass die Enum ist einer der Typen, dass ist eine Maßeinheit, sonst könnte passieren, in einen DialogResult-Wert und der Konverter würde ausflippen bei der Laufzeit.
Statt, wenn Sie möchten, eine UnitConverter können, zu konvertieren, um eine UnitOfMeasure gegeben lambdas für andere Einheiten der Messung, ich würde geben die Maßeinheit als "marker-Klassen"; kleine Staatenlose "Token", die nur einen Sinn haben, dass Sie Ihre eigene Art und leiten sich von den Eltern:
Kannst du in Fehler-überprüfung (überprüfen, dass die Eingabe-Einheit hat eine Umwandlung angegeben ist, überprüfen Sie, dass eine Konvertierung Hinzugefügt werden, ist für eine ME mit den gleichen Eltern als die Basis-Typ, etc etc) wie Sie sehen, passen. Sie können auch ableiten UnitConverter zu erstellen TemperatureConverter, so dass Sie hinzufügen, statische, compile-time type checks und die Vermeidung der run-time checks, UnitConverter verwenden würde.
Klingt es wie Sie wollen, so etwas wie:
Jedoch, möchten Sie vielleicht zu prüfen, nicht nur mit
decimal
, aber mit einer Art, die kennt die Einheiten, so dass Sie nicht versehentlich (sagen) gelten die "Celsius to Kelvin" Konvertierung zu nicht-Celsius-Wert. Möglicherweise haben Sie einen Blick auf die F# Maßeinheiten Ansatz für inspiration.Konnte man einen Blick auf Units.NET. Es ist auf GitHub und NuGet. Es bietet die meisten gängigen Einheiten und Umrechnungen, unterstützt sowohl statische Typisierung und Nummerierung der Einheiten und die Analyse/das drucken der Abkürzungen. Sie analysieren nicht Ausdrücken, obwohl, und Sie können nicht erweitern bestehende Klassen von Einheiten, aber Sie können erweitern es mit neuen Einheiten Dritter.
Beispiel Konvertierungen:
Normalerweise wollte ich hinzufügen, diese als Kommentar zu Danny Tuppeny post, aber es scheint, dass ich bin nicht in der Lage, fügen Sie diese als Kommentar.
Verbesserte ich die Lösung von @Danny Tuppeny ein wenig. Ich wollte nicht um jeden conversion-Gespräch mit zwei Faktoren, da nur eine nötig sein sollte. Auch die parameter vom Typ Func scheint nicht nötig zu sein, es macht es nur komplizierter für den Benutzer.
Also mein Aufruf sieht wie folgt aus:
Habe ich auch noch eine Methode, um den Umrechnungsfaktor zwischen den Einheiten.
Dies ist die modifizierte UnitConverter Klasse:
Kann man definieren eine physikalische Einheiten generischen Typ, so dass, wenn man für jede Einheit einen Typ implementiert
new
und beinhaltet unter anderem ein translation-Methode zwischen diesem Gerät und "Basisstation" von dieser Art ist, kann man arithmetische Operationen ausgeführt werden, die auf Werte, die in den verschiedenen Einheiten ausgedrückt und habe Sie umgewandelt wie nötig, mit dem Text-system, so dass eine variable vom TypAreaUnit<LengthUnit.Inches>
würde nur Dinge akzeptieren, dimensioniert in square inches, aber wenn man sagtemyAreaInSquareInches= AreaUnit<LengthUnit.Inches>.Product(someLengthInCentimeters, someLengthInFathoms);
es würde automatisch übersetzen diese andere Einheit, die vor der Durchführung der Multiplikation. Es kann tatsächlich sehr gut funktionieren, wenn mit der Methode-call-syntax, da Methoden wieProduct<T1,T2>(T1 p1, T2 p2)
Methode akzeptieren können generische Typparameter Ihre Operanden. Leider gibt es keine Möglichkeit, Operatoren, generische, noch ist es für einen Typ wieAreaUnit<T> where T:LengthUnitDescriptor
zu definieren, ein Mittel für die Umwandlung in eine oder aus einer anderen beliebigen generischen TypsAreaUnit<U>
. EinAreaUnit<T>
definieren könnte, Konvertierungen zu und von z.B.AreaUnit<Angstrom>
, aber es gibt keine Möglichkeit dem compiler gesagt werden könnte, dass code, der eineAreaUnit<Centimeters> and wants
AreaUnit` kann konvertieren Zoll zu Angström und dann in Zentimetern.