Fortgeschrittene Formatierung und Parsing von Strings
-
Hallo zusammen,
Kennt ihr gute Quellen über die Formatierung und Parsing von Strings in .Net? Es geht mir wirklich um fortgeschrittene Themen, wie
IFormattable
,IFormatProvider
undICustomFormatter
. In der MSDN gibt es zwar ein paar gute Seiten, wie z.B:
http://msdn.microsoft.com/en-us/library/26etazsy.aspxAber irgendwie steig ich noch nicht ganz durch. Ich sehe verschiedene Probleme:
1. Wie muss ein eigenerIFormatProvider
aussehen, damit er korrekt funktioniert? Wenn manString.Format
verwendet, kann man nur einenIFormatProvider
angeben, wie weiss dann die Funktion, wann sie welches Format anwenden soll? Wie kann ich pro Typ unterschiedliche Formatierungen mitgeben?
2. Wie kann ich sinnvoll die Formatierung der Primitiven steuern. Ich möchte zum Beispiel standardmässigCultureInfo.InvariantCulture
verwenden, aber ohne beiThread.CurrentCulture
reinzupfuschen. Muss ich meine Funktionen für primitive Typen überladen oder kann ich das mit einer machen, welche einen bestimmten Typ erwartet?IFormattable
?Object
? Wie sieht das Ganze aus, wenn es vom String wieder in den primitiven Typ geht?
3. Gibt es überhaupt eine vorbereitete Möglichkeit Strings zu parsen? Ingendwelche Interfaces oder sowas?Vielen Dank im Voraus.
Grüssli
-
Hi,
Genauer habe ich mich damit noch nicht befasst, daher kann ich dir nur zum Teil antworten.
Dravere schrieb:
Aber irgendwie steig ich noch nicht ganz durch. Ich sehe verschiedene Probleme:
1. Wie muss ein eigenerIFormatProvider
aussehen, damit er korrekt funktioniert? Wenn manString.Format
verwendet, kann man nur einenIFormatProvider
angeben, wie weiss dann die Funktion, wann sie welches Format anwenden soll? Wie kann ich pro Typ unterschiedliche Formatierungen mitgeben?Ich glaub das ist nicht vorgesehen das die Parameter bei string.Format mit unterschiedlichen IFormatProvider ausgestattet werden. Du kannst aber bei den Typen eventuell das ToString() verwenden und dort den Formatter angeben.
Dravere schrieb:
2. Wie kann ich sinnvoll die Formatierung der Primitiven steuern. Ich möchte zum Beispiel standardmässig
CultureInfo.InvariantCulture
verwenden, aber ohne beiThread.CurrentCulture
reinzupfuschen. Muss ich meine Funktionen für primitive Typen überladen oder kann ich das mit einer machen, welche einen bestimmten Typ erwartet?IFormattable
?Object
? Wie sieht das Ganze aus, wenn es vom String wieder in den primitiven Typ geht?Du brauchst in CurrentCulture nichts ändern. CultureInfo ist auch ein IFormatProvider (Siehe http://msdn.microsoft.com/de-de/library/system.globalization.cultureinfo(v=vs.80).aspx), also kannst du sehr einfach steuern in welcher Culture es Formatiert werden soll.
Z.B.:
string->doubledouble value = double.Parse("1.2", new CultureInfo("en-US"));
double->string
var d = 1.5; string value = d.ToString(new CultureInfo("en-US"));
Dravere schrieb:
3. Gibt es überhaupt eine vorbereitete Möglichkeit Strings zu parsen? Ingendwelche Interfaces oder sowas?
Ich finde sind die "TryParse" Methoden am besten.
Es gibt auch noch die "Converter" Klasse, diese basiert (unter anderem) darauf das die Typen IConvertibe implementiert haben, ich bevorzuge aber wie gesagt die [Try]Parse.//Dazu:
Siehe auch: http://www.c-plusplus.net/forum/291901
-
David W schrieb:
Ich glaub das ist nicht vorgesehen das die Parameter bei string.Format mit unterschiedlichen IFormatProvider ausgestattet werden. Du kannst aber bei den Typen eventuell das ToString() verwenden und dort den Formatter angeben.
David W schrieb:
Du brauchst in CurrentCulture nichts ändern. CultureInfo ist auch ein IFormatProvider (Siehe http://msdn.microsoft.com/de-de/library/system.globalization.cultureinfo(v=vs.80).aspx), also kannst du sehr einfach steuern in welcher Culture es Formatiert werden soll.
Z.B.:
string->doubledouble value = double.Parse("1.2", new CultureInfo("en-US"));
double->string
var d = 1.5; string value = d.ToString(new CultureInfo("en-US"));
ToString(IFormatProvider provider)
kommt leider aus keinem Interface, sondern ist für jeden Typ explizit vorhanden. Bei z.B.System.Enum
ist dieses sogar als obsolete gekennzeichnet. Es gibt nur einToString(string format, IFormatProvider provider)
inIFormattable
. Soweit ich es verstanden habe, kann man dann fürformat
null
übergeben.Wenn nun aber jemand
IFormattable
implementiert, wie müsste dieser nun genau vorgehen. Das ist mir irgendwie noch nicht so ganz klar. Und kann ich mitIFormattable
alle abfangen?David W schrieb:
Ich finde sind die "TryParse" Methoden am besten.
Es gibt auch noch die "Converter" Klasse, diese basiert (unter anderem) darauf das die Typen IConvertibe implementiert haben, ich bevorzuge aber wie gesagt die [Try]Parse.Die
Parse
undTryParse
sind aber statische Methoden und gehören daher zu keinem Interface.IConvertible
ist nicht CLS-compliant. Ich suche ein gutes Pendant zuIFormattable
und co.Ich hätte zum Beispiel am liebsten etwas gehabt, was ein
DateTime
automatisch als Unix Timestamp ausgibt und entsprechend wieder einliest, ohne dabei das Datum immer zuerst umrechnen zu müssen, bzw. dass dies irgendwo weggekapselt in der Formattierung passiert. Für die Ausgabe könnte ich es womöglich noch hinbekommen, aber beim Einlesen sehe ich schwarz. Wobei ich auch bei der Ausgabe Probleme sehe. Wenn ich einIFormattable
Object
habe, was für einenIFormatProvider
muss ich mitgeben, damit bei einemDateTime
die richtige Unix Timestamp Formatierung angewendet wird, wenn es allerdings ein anderer Typ ist, dass dann trotzdem die korrekte Formatierung drankommt (also z.B. beiInt32
CultureInfo.InvariantCulture
).Nur damit es klar ist, ich weiss schon, wie man ein Unix Timestamp ausgeben und einlesen kann. Mir geht es wirklich nur darum, dass ich dies gerne wegkapseln möchte. Und ich hatte gehofft, dass es da schon grundsätzliche Schnittstellen gäbe.
Grüssli
-
Dravere schrieb:
ToString(IFormatProvider provider)
kommt leider aus keinem Interface, sondern ist für jeden Typ explizit vorhanden.Was ich logisch finde, da jeder Typ anders geschrieben wird, wie soll man ein int auch mit einem TimeSpan vergleichen.
Dravere schrieb:
Bei z.B.
System.Enum
ist dieses sogar als obsolete gekennzeichnet. Es gibt nur einToString(string format, IFormatProvider provider)
inIFormattable
. Soweit ich es verstanden habe, kann man dann fürformat
null
übergeben.Ruf beim Enum einfach ohne FormatProvider auf, der Rückgabewert unterscheidet sich nicht bei den verschiedenen Cultures. Daher ist der IFormatProvider schlichtweg unnötig.
Dravere schrieb:
Wenn nun aber jemand
IFormattable
implementiert, wie müsste dieser nun genau vorgehen. Das ist mir irgendwie noch nicht so ganz klar. Und kann ich mitIFormattable
alle abfangen?
...
Ich hätte zum Beispiel am liebsten etwas gehabt, was einDateTime
automatisch als Unix Timestamp ausgibt und entsprechend wieder einliest, ohne dabei das Datum immer zuerst umrechnen zu müssen, bzw. dass dies irgendwo weggekapselt in der Formattierung passiert. Für die Ausgabe könnte ich es womöglich noch hinbekommen, aber beim Einlesen sehe ich schwarz. Wobei ich auch bei der Ausgabe Probleme sehe. Wenn ich einIFormattable
Object
habe, was für einenIFormatProvider
muss ich mitgeben, damit bei einemDateTime
die richtige Unix Timestamp Formatierung angewendet wird, wenn es allerdings ein anderer Typ ist, dass dann trotzdem die korrekte Formatierung drankommt (also z.B. beiInt32
CultureInfo.InvariantCulture
).Du müsstest Problemlos ein "UnixTimeStampFormatter" schreiben können, auf dieser Seite findest du ein vollständiges Beispiel: http://msdn.microsoft.com/en-us/library/system.iformatprovider.aspx
Aber das betrifft nur die Richtung DateTime -> String. Für den weg zurück wüsste ich gerade nichts. Ne Idee wäre lediglich das du die "UnixTimeStampFormatter" Klasse noch um ein "Parse", "TryParse", "ParseExact" sowie "TryParseExact" versehen kannst.Was wäre deine Wunschlösung? Die Parse und TryParse sind zwar explizit für jeden Typen, aber das ist doch in Ordnung, wüsste nicht das man da etwas generischeres braucht da man an der Stelle wo man es anwendet eh den genauen Typen kennen muss.
-
David W schrieb:
Dravere schrieb:
ToString(IFormatProvider provider)
kommt leider aus keinem Interface, sondern ist für jeden Typ explizit vorhanden.Was ich logisch finde, da jeder Typ anders geschrieben wird, wie soll man ein int auch mit einem TimeSpan vergleichen.
Es geht ja nicht ums Vergleichen, sondern eine gemeinsame Schnittstelle für das Formatieren und das ist mit dieser Funktion nicht gegeben.
David W schrieb:
Dravere schrieb:
Bei z.B.
System.Enum
ist dieses sogar als obsolete gekennzeichnet. Es gibt nur einToString(string format, IFormatProvider provider)
inIFormattable
. Soweit ich es verstanden habe, kann man dann fürformat
null
übergeben.Ruf beim Enum einfach ohne FormatProvider auf, der Rückgabewert unterscheidet sich nicht bei den verschiedenen Cultures. Daher ist der IFormatProvider schlichtweg unnötig.
Das ist aber nicht mein Ziel ... damit muss ich Überladungen schreiben für Enum-Werte.
David W schrieb:
Du müsstest Problemlos ein "UnixTimeStampFormatter" schreiben können, auf dieser Seite findest du ein vollständiges Beispiel: http://msdn.microsoft.com/en-us/library/system.iformatprovider.aspx
Aber das betrifft nur die Richtung DateTime -> String.Wie das für einen einzelnen Typ geht, weiss ich schon. Aber was ist bei sowas:
var text = String.Format(new UnixTimeStampFormatter(), "{0}: {1}", DateTime.Today, 33.4);
Wie kann ich hier dafür sorgen, dass die 33.4 mit der
CultureInfo.InvarantCultur
formatiert wird, ohne dass ichThread.CurrentCulture
anfasse?David W schrieb:
Was wäre deine Wunschlösung? Die Parse und TryParse sind zwar explizit für jeden Typen, aber das ist doch in Ordnung, wüsste nicht das man da etwas generischeres braucht da man an der Stelle wo man es anwendet eh den genauen Typen kennen muss.
Ich möchte sowas anbieten:
TValue GetValue<TValue>(string key, Parser<TValue> parser) { var text = dict[key]; return parser.Parse(text, CultureInfo.InvarianCulture); } void SetValue<TValue>(string key, TValue value) { dict[key] = // ... ja wie? Wie sichere ich, dass hier InvariantCultur verwendet wird? }
Und hatte gehofft, dass es bereits etwas von der Standardbibliothek gibt, was ich ausbauen oder wiederverwenden kann, damit ich mich gut ins Framework einpasse.
Wobei ich beiSetValue
eben auch noch gerne sowas anbieten möchte:void SetValue(string key, string format, params object[] objs) { dict[key] = String.Format(/* ja was? */, format, objs); }
Wobei eben immer
InvariantCulture
verwendet wird, aber z.B. bei einemDateTime
das Unix Timestamp Format. Und wenn möglich, möchte ich noch für ein paar weitere eigene Typen eine spezielle Formatierung anbieten. Und das Ganze dann eben auch in die andere Richtung.Grüssli
-
[quote="David W"]
Dravere schrieb:
David W schrieb:
Dravere schrieb:
Bei z.B.
System.Enum
ist dieses sogar als obsolete gekennzeichnet. Es gibt nur einToString(string format, IFormatProvider provider)
inIFormattable
. Soweit ich es verstanden habe, kann man dann fürformat
null
übergeben.Ruf beim Enum einfach ohne FormatProvider auf, der Rückgabewert unterscheidet sich nicht bei den verschiedenen Cultures. Daher ist der IFormatProvider schlichtweg unnötig.
Das ist aber nicht mein Ziel ... damit muss ich Überladungen schreiben für Enum-Werte.
Erstmal irrelevant da du derzeit ja noch nicht einmal eine gemeinsame Schnittstelle hast
David W schrieb:
Wie das für einen einzelnen Typ geht, weiss ich schon. Aber was ist bei sowas:
var text = String.Format(new UnixTimeStampFormatter(), "{0}: {1}", DateTime.Today, 33.4);
Wie kann ich hier dafür sorgen, dass die 33.4 mit der
CultureInfo.InvarantCultur
formatiert wird, ohne dass ichThread.CurrentCulture
anfasse?Nicht ganz schön aber funktioniert:
var value = 33.4; var text = string.Format(new CultureInfo(1033), "{0}: {1}", DateTime.Today, value.ToString(CultureInfo.InvariantCulture));
Was du eventuell machen könntest wäre sowas wie ich es auch in meiner DW.Configuration gemacht habe, und zwar registrierst du beim Applikations start für jeden Typen einen "Marshaller" der es entsprechend Konvertiert.
Es gibt ein generischen IMarshaller, der nimmt ein XElement und gibt Object zurück (da fällt mir ein das ich es eventuell noch generisch machen kann ^^)Z.B.:
public class DateTimeMarshaller : IMarshaller { public object Extract(XElement settingNode) { var text = settingNode.Value; if (!string.IsNullOrWhiteSpace(text)) { DateTime converted = DateTime.Now; if (DateTime.TryParse(text, new CultureInfo(1033), DateTimeStyles.None, out converted)) return converted; } return null; } public void Append(XElement settingNode, object value) { if (value is DateTime) { var converted = (DateTime)value; settingNode.Value = converted.ToString(new CultureInfo(1033)); } else settingNode.Value = value.ToString(); } }
Im "SettingsManager" sagt man dann welcher Key mit welchen Marshaller gelesen und geschrieben werden soll:
public static class SettingsManager { public static void Load() { var reader = new SettingsReader(GetFilePath(), new DefaultsContainer()); RegisterMarshaller(reader); reader.Read(); } public static void Save() { var writer = new SettingsWriter(GetFilePath()); RegisterMarshaller(writer); writer.Write(); } private static void RegisterMarshaller(SettingsHandler writer) { writer.SetKeysMarshaller(new BooleanMarshaller(), "Boolean"); // Hier kann man eine Liste von Keys angeben die dann mit dem Marshaller Konvertiert werden writer.SetKeysMarshaller(new DateTimeMarshaller(), "DateTime"); writer.SetKeysMarshaller(new DoubleMarshaller(), "Double"); writer.SetKeysMarshaller(new IntegerMarshaller(), "Integer"); writer.SetKeysMarshaller(new LongMarshaller(), "Long"); writer.SetKeysMarshaller(new ShortMarshaller(), "Short"); writer.SetKeysMarshaller(new StringMarshaller(), "String"); } private static string GetFilePath() { return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Settings.cfg"); } }
(https://github.com/DavidWDev/DW.Configurations/blob/master/DW.Configurations.Demo/SettingsManager.cs)
Ich habe die Verwendung ganz einfach:
SettingsManager.Load(); // Settings laden var value = Settings.Get<int>("Integer"); // Wert lesen, "Integer" ist der Key
(https://github.com/DavidWDev/DW.Configurations/blob/master/DW.Configurations.Demo/App.xaml.cs)
So ähnlich könntest du es auch Lösen.
-
So ähnlich habe ich es bis jetzt gelöst
Meine "Marshaller" verbinden sich allerdings gleich mit dem Typ. Also ein:
RegisterMarshaller(Type type, IMarshaller marshaller);
Ich habe mich allerdings bisher eben gefragt, ob ich damit nicht Features aus der .Net Bibliothek umschiffe. Ob man es nicht direkt über
IFormattable
oder dergleichen lösen könnte.Grüssli
-
Interessant wie man doch zu ähnlichen Lösungen kommt ^^
Hast du den "IMarshaller" über Generics gelöst?
-
David W schrieb:
Hast du den "IMarshaller" über Generics gelöst?
Wie sollte man dies denn über Generics hinbekommen? Man muss den ja irgendwie in ein Dictionary quetschen. Ich habe mir aber eine Hilfsklasse erstellt in der Hierarchie. Also etwas in diese Richtung:
interface ITextConverter { object ToObject(string text); string ToText(object obj); } abstract class AbstractTextConverter<TObject> : ITextConverter { protected abstract TObject ToTypedObject(string text); protected abstract string ToText(TObject obj); public object ToObject(string text) { return ToTypedObject(text); } public string ToText(object obj) { if(obj is TObject) { return ToText((TObject)obj); } throw new InvalidOperationException("Type not supported!"); } }
Grüssli
-
Ich dachte an irgendwie sowas:
interface IConverter<T> { T FromText(string text); string ToText<T>(T obj); } public class IntegerConverter : IConverter<int> { public int FromText(string text) { ... } public string ToText(int obj) { ... } }
Hab es grad hier im Forum getippt aber nicht getestet ob es überhaupt baut.
Aber jetzt wo ich es seh merk ich auch das man es so nicht in ein Dictionary bekommt - vermutlich hatte ich es aus dem selben Grund auch damals nicht gemacht ^^