DictionaryString - REST-Helferlein

    • VB.NET

    Es gibt 2 Antworten in diesem Thema. Der letzte Beitrag () ist von nafets.

      DictionaryString - REST-Helferlein

      Bei meiner Arbeit mit REST muss ich immer so lange, unleserliche queryStrings aufbauen, ungefähr dieser Art: query=bremer+dom&cat=web&pl=ext-ff&language=deutsch&extVersion=1.3.0.
      Ich finde das sehr unleserlich und schwierig zu warten. Auch muss ich derlei Strings auch immer wieder modifizieren.
      Recht betrachtet beschreibt so ein String ja ein Dictionary<string, string> in kürzest-möglicher Form - also durchaus pfiffig!
      Nur die Leserlichkeit! Eine kleine Änderung - , als Separator - brächte schon Erleichterung: query=bremer+dom, cat=web, pl=ext-ff, language=deutsch, extVersion=1.3.0

      Ein andere REST-Übung ist das Adden von Headern oder Parametern

      VB.NET-Quellcode

      1. request.AddHeader("Accept-Encoding=gzip,deflate,br")
      2. request.AddHeader("Connection=keep-alive")
      3. request.AddHeader("Content-Type=application/x-www-form-urlencoded")
      4. request.AddHeader("Host=www.startpage.com")
      5. request.AddHeader("Origin=https://www.startpage.com")
      6. request.AddHeader("Referer=https://www.startpage.com/")
      7. request.AddHeader("Startpage-Extension=ext-ff")
      8. request.AddHeader("TE=Trailers")
      9. '...
      Lauter so Zeug.
      Derlei Header/Parameter sind offsichtlich auch nix weiter als String-Dictionaries.

      Also habich gedacht, ein DictionaryString muss her, der String und Dictionary verquickt. Sodass man das Ding kompakt initialisieren kann.
      Und er soll leserlich befüllbar sein, aber syntaktisch korrekten Output liefern. Output-Separator soll einstellbar sein, und InputSeparator(en) auch.

      Herausgekommen ist dieses:

      VB.NET-Quellcode

      1. Public Class DictionaryString
      2. Public Separators As String() = {", "}
      3. Public ReadOnly Dictionary As New Dictionary(Of String, String)
      4. Public Sub New(Optional separator As String = ", ")
      5. Me.Separators = {separator}
      6. End Sub
      7. Public Sub New(separators As IEnumerable(Of String), Optional entries As String = Nothing)
      8. Me.Separators = separators.ToArray
      9. If entries IsNot Nothing Then Merge(entries)
      10. End Sub
      11. Public Shared Widening Operator CType(ds As DictionaryString) As String
      12. Return ds.ToString
      13. End Operator
      14. Public Shared Operator +(x As DictionaryString, y As String) As DictionaryString
      15. x.Merge(y) : Return x
      16. End Operator
      17. Private Sub Merge(str As String)
      18. Dim entries = str.Split(Separators, StringSplitOptions.RemoveEmptyEntries)
      19. For i = 0 To entries.Length - 1
      20. Dim splits = entries(i).Split({"="c}, 2)
      21. If splits.Length <> 2 Then Throw New ArgumentException($"Der {i + 1}te Eintrag enthält kein Trennzeichen '='")
      22. Item(splits(0)) = splits(1).Trim
      23. Next
      24. End Sub
      25. Public ReadOnly Property Value As String
      26. Get
      27. Return ToString()
      28. End Get
      29. End Property
      30. Public Overrides Function ToString() As String
      31. 'Separators(0) ist der Output-Separator
      32. Return String.Join(Separators(0), From kvp In Dictionary Select String.Concat(kvp.Key, "=", kvp.Value))
      33. End Function
      34. Default Public Property Item(key As String) As String
      35. Get
      36. Dim s As String = Nothing
      37. Dictionary.TryGetValue(key, s)
      38. Return s
      39. End Get
      40. Set(value As String)
      41. key = key.Trim
      42. If value = "" Then Dictionary.Remove(key) Else Dictionary(key) = value
      43. End Set
      44. End Property
      45. End Class
      Das Besondere sind die implementierten Operatoren: Widening CType erlaubt, den DictionaryString ohne weiteres an eine String-Variable zuzuweisen.
      Der +Operator impliziert +=, und das ermöglicht, Strings als Einträge einzumergen.
      Den Nutzen sieht man vlt. in der Test-Methode:

      VB.NET-Quellcode

      1. ''' <summary>Empfehlung: im Haltemodus durchsteppen und Lokal-Fenster beobachten</summary>
      2. Sub TestDicString()
      3. Dim ds = New DictionaryString({"&", ", "}, "a=b, x=ü, gg=55")
      4. ds += "gut=schlecht, weiss=schwarz, Kommunismus=Faschismus, Wahrheit=Geschmacksache"
      5. ds += "raiders=twix, Arbeit=Freiheit, Freiheit=Glücksache, egal=88"
      6. ds += "gut=, weiss=, Kommunismus=, Wahrheit=, raiders=, Arbeit=, egal=" ' lange einträge löschen, wg einfacher debuggen
      7. Dim s = ds("x") ' Standard-Dictionary-Verhalten
      8. s = ds!x ' alternative Schreibweise, vb-spezifisch
      9. s = ds!x & "45"
      10. ds += "a=c, x=, ja=nein, zz=" ' a wird geändert, x gelöscht, ja zugefügt, zz macht nix
      11. s = ds!x ' x ist verschwunden, Ergebnis also Nothing
      12. ds!x = "lkj" ' x wird zugefügt (Standard-Dictionary-Verhalten)
      13. s = ds ' Ausgabe als String
      14. End Sub
      Zeile #3: Initialisiert mit zwei Separatoren und drei Einträgen - letztere mit dem besser lesbaren , -Separator. (Output-Separator ist immer der erste der Separator-Liste)
      #4+5: Fügt Einträge hinzu. Gute Lesbarkeit wg , -Separator, und weil auf zwei Zeilen verteilt.
      Der DictionaryString hat nun 11 Einträge, und seine Ausgabe ist sone typische REST-Parameter-String-Gräuseligkeit (die man leider ja so benötigt): a=b&x=ü&gg=55&gut=schlecht&weiss=schwarz&Kommunismus=Faschismus&Wahrheit=Geschmacksache&raiders=twix&Arbeit=Freiheit&Freiheit=Glücksache&egal=88

      Zeile#6 entfernt die langen Einträge, einerseits um das Multi-Entfern-Feature vorzustellen, andererseits um die weiteren Operationen im Debugger leichter beobachtbar zu machen.

      Also += zeigt sich hier als "Merge-Operator": Ich kann in einer Anweisung (zb Zeile#10) Einträge ändern, zufügen, löschen - und ist trotzdem leidlich leserlich.
      Und ich kann (#13) den DictionaryString als normalen String benutzen - ihn etwa an eine Variable zuweisen
      Dateien
      Ich hab mal die Klasse DictionaryString nach C# übersetzt und die Klammersetzung {...} nach meiner Intention gehübscht:
      DictionaryString

      C#-Quellcode

      1. // StringDictionary soll mit 1 String befüllbar sein, Output-Separator, Input-Separator(en)
      2. // 1-String-Syntax zum Ändern/Löschen/Zufügen (Merge) mehrerer Einträge
      3. // Abruf einzelner Werte, Null bei nicht da
      4. using System;
      5. using System.Collections.Generic;
      6. using System.Linq;
      7. namespace DictionaryStringTester
      8. {
      9. public class DictionaryString
      10. {
      11. public string[] Separators = new[] { ", " };
      12. public readonly Dictionary<string, string> Dictionary = new Dictionary<string, string>();
      13. public DictionaryString(string separator = ", ")
      14. {
      15. this.Separators = new[] { separator };
      16. }
      17. public DictionaryString(IEnumerable<string> separators, string entries = null)
      18. {
      19. this.Separators = separators.ToArray();
      20. if (entries is object)
      21. {
      22. this.Merge(entries);
      23. }
      24. }
      25. public static implicit operator string(DictionaryString ds)
      26. {
      27. return ds.ToString();
      28. }
      29. public static DictionaryString operator +(DictionaryString x, string y)
      30. {
      31. x.Merge(y);
      32. return x;
      33. }
      34. private void Merge(string str)
      35. {
      36. var entries = str.Split(this.Separators, StringSplitOptions.RemoveEmptyEntries);
      37. for (int i = 0, loopTo = entries.Length - 1; i <= loopTo; i++)
      38. {
      39. var splits = entries[i].Split(new[] { '=' }, 2);
      40. if (splits.Length != 2)
      41. {
      42. throw new ArgumentException($"Der {i + 1}. Eintrag enthält kein Trennzeichen '='");
      43. }
      44. this[splits[0]] = splits[1].Trim();
      45. }
      46. }
      47. public string Value { get { return this.ToString(); } }
      48. public override string ToString()
      49. {
      50. // Separators(0) ist der Output-Separator
      51. return string.Join(this.Separators[0], from kvp in this.Dictionary
      52. select string.Concat(kvp.Key, "=", kvp.Value));
      53. }
      54. public string this[string key]
      55. {
      56. get
      57. {
      58. string s = null;
      59. this.Dictionary.TryGetValue(key, out s);
      60. return s;
      61. }
      62. set
      63. {
      64. key = key.Trim();
      65. if (string.IsNullOrEmpty(value))
      66. {
      67. this.Dictionary.Remove(key);
      68. }
      69. else
      70. {
      71. this.Dictionary[key] = value;
      72. }
      73. }
      74. }
      75. }
      76. }
      Interessant ist die unterschiedliche Übersetzung der beiden Konstruktoren:
      Studio-AddOn-Übersetzer:
      Studio-AddOn

      C#-Quellcode

      1. public DictionaryString(string separator = ", ")
      2. {
      3. this.Separators = new[] { separator };
      4. }
      5. public DictionaryString(IEnumerable<string> separators, string entries = null)
      6. {
      7. this.Separators = separators.ToArray();
      8. if (entries is object)
      9. {
      10. this.Merge(entries);
      11. }
      12. }
      IlSpy:
      IlSpy

      C#-Quellcode

      1. public DictionaryString(string separator = ", ")
      2. {
      3. Separators = new string[1]
      4. {
      5. ", "
      6. };
      7. Dictionary = new Dictionary<string, string>();
      8. Separators = new string[1]
      9. {
      10. separator
      11. };
      12. }
      13. public DictionaryString(IEnumerable<string> separators, string entries = null)
      14. {
      15. Separators = new string[1]
      16. {
      17. ", "
      18. };
      19. Dictionary = new Dictionary<string, string>();
      20. Separators = separators.ToArray();
      21. if (entries != null)
      22. {
      23. Merge(entries);
      24. }
      25. }
      Was ich am Studio-Übersetzer merkwürdig finde, ist der Test if (entries is object)
      aus der Vorlage If entries IsNot Nothing Then
      ====
      Die Vorlage If value = "" Then
      wird vom Studio-Übersetzer elegant mit if (string.IsNullOrEmpty(value)) übersetzt,
      IlSpy braucht dazu den Namespace Microsoft.VisualBasic.CompilerServices.Operators,
      ohne dass dieser Namespace im Projekt vorkommt. ?(
      Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
      Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
      Ein guter .NET-Snippetkonverter (der ist verfügbar).
      Programmierfragen über PN / Konversation werden ignoriert!

      RodFromGermany schrieb:

      IlSpy braucht dazu den Namespace Microsoft.VisualBasic.CompilerServices.Operators,
      ohne dass dieser Namespace im Projekt vorkommt.

      Das macht der VB-Compiler automatisch bei vielen Operatoren und kommt soweit ich weiß von der Option Strict Off-Unterstützung her.