String verwenden oder gleich alles mit StringBuilder machen

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 13 Antworten in diesem Thema. Der letzte Beitrag () ist von EaranMaleasi.

    String verwenden oder gleich alles mit StringBuilder machen

    Mir geht gerade eine Frage durch den Kopf. StringBuilder ist in .Net viel schneller als das normale String.
    Sollte man dann nicht gleich alles als StringBuilder anlegen oder gibts weiterhin gute Gründe für den normalen String?
    Aktuelles Projekt: Z80 Disassembler für Schneider/Amstrad CPC :love:
    Code mit normalen Strings ist besser lesbar. Fast alle Methoden erwarten Strings - nicht StringBuilder. String stellt 200 KlassenMember bereit - ist also weitaus mächtiger als StringBuilder.

    Ich verwende StringBuilder sogut wie nie. Wenn ich Strings zusammenbauen muss, dann nehme ich eine List(Of Object), pack da meine Daten in richtiger Reihenfolge rein, und verwandle es am Ende in einen STring, mittels String.Join() oder String.Concat().
    Dassis viel komfortabler als StringBuilder, und vermutlich sogar schneller.

    Wobei die Geschwindigkeit ja eh egal ist.
    @oobdoo Ich verwende StringBuilder auch eher weniger, in einem übernommenen Projekt war mal einer, wo Strings aus vielen Zeilen aufgebaut wurden.
    Ich bin da auch eher bei einer List(Of String) und setz die dann wie der EDR zusammen.
    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!
    Noch nie benutzt... und trotzdem sind meine Programme nicht langsam. Ein Performance Verlust kommt dann eher von selbst geschriebenen Methoden oder DB/Dateisystem Zugriffen und nicht von Strings.

    Was programmierst du das die Nutzung eines StringBuilder spürbare Performance Vorteile bringt?
    "Gib einem Mann einen Fisch und du ernährst ihn für einen Tag. Lehre einen Mann zu fischen und du ernährst ihn für sein Leben."

    Wie debugge ich richtig? => Debuggen, Fehler finden und beseitigen
    Wie man VisualStudio nutzt? => VisualStudio richtig nutzen
    Ein StringBuilder zum Abholen von Texten aus nativen DLLs ist eine feine Sache.
    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!

    oobdoo schrieb:

    StringBuilder ist in .Net viel schneller als das normale String.
    Nein, das kannst du so pauschal nicht sagen. StringBuilder ist nur schneller, wenn viele Strings aneinander gereiht werden. Z.B. ​"A" + "B" + "C" + "D" + x + varabley + ": " + NewLine + ...... Oder wenn du dir SQL-Statements dynamisch selber zusammen baust und da kommen dann 200 ​WHERE Bedingungen rein, die du mit + aneinander kettest. Für ein paar Strings ist der StringBuilder nicht schneller.
    Kann ich bestätigen.

    Den Test habe ich vor Jahren schon gemacht. Jedoch nur String und Stringbuilder.
    Hab um die String.Join erweitert.

    Hinweis: Test mit Release-Mode und direkt die Exe ausgeführt

    Freundliche Grüsse

    exc-jdbi



    Spoiler anzeigen

    vb

    VB.NET-Quellcode

    1. Option Strict On
    2. Option Explicit On
    3. Imports System.Text
    4. Public Module Module1
    5. Private Rand As New Random
    6. Private AlphaString As String
    7. Private LstStr As List(Of List(Of Int32))
    8. Public Sub Main()
    9. Dim size = 1000
    10. Dim length = 1234
    11. AlphaStrings()
    12. RngCharNumber(size, length)
    13. 'Der eigentliche Test
    14. Console.WriteLine("VB.Net-Test")
    15. Console.WriteLine("***********")
    16. Console.WriteLine($"ListOfT.Length = {size}")
    17. Console.WriteLine($"String.Length = {length}")
    18. Console.WriteLine()
    19. TestString()
    20. TestStringBuilder()
    21. TestStringJoin()
    22. Console.ReadLine()
    23. End Sub
    24. Private Sub TestString()
    25. Dim str, s As String
    26. Dim sw = Stopwatch.StartNew
    27. str = String.Empty
    28. For i = 0 To LstStr.Count - 1
    29. s = String.Empty
    30. For j = 0 To LstStr(i).Count - 1
    31. s &= AlphaString(LstStr(i)(j))
    32. Next
    33. str &= s
    34. Next
    35. Console.WriteLine($"{NameOf(TestString)}: {sw.ElapsedMilliseconds}ms")
    36. GC.Collect()
    37. End Sub
    38. Private Sub TestStringBuilder()
    39. Dim str, s As StringBuilder
    40. Dim sw = Stopwatch.StartNew
    41. str = New StringBuilder
    42. For i = 0 To LstStr.Count - 1
    43. s = New StringBuilder
    44. For j = 0 To LstStr(i).Count - 1
    45. s.Append(AlphaString(LstStr(i)(j)))
    46. Next
    47. str.Append(s)
    48. Next
    49. Console.WriteLine($"{NameOf(TestStringBuilder)}: {sw.ElapsedMilliseconds}ms")
    50. GC.Collect()
    51. End Sub
    52. Private Sub TestStringJoin()
    53. Dim sw = Stopwatch.StartNew
    54. Dim lstr As New List(Of List(Of Char))(LstStr.Count)
    55. For i = 0 To LstStr.Count - 1
    56. lstr.Add(New List(Of Char)(LstStr(i).Count))
    57. For j = 0 To LstStr(i).Count - 1
    58. lstr(i).Add(AlphaString(LstStr(i)(j)))
    59. Next
    60. Next
    61. Dim stmp = lstr.Select(Function(s) String.Join("", s)).ToArray
    62. Dim str = String.Join("", stmp)
    63. Console.WriteLine($"{NameOf(TestStringJoin)}: {sw.ElapsedMilliseconds}ms")
    64. GC.Collect()
    65. End Sub
    66. Private Sub RngCharNumber(size As Int32, length As Int32)
    67. LstStr = New List(Of List(Of Int32))(size)
    68. For i = 0 To size - 1
    69. LstStr.Add(New List(Of Int32)(length))
    70. For j = 0 To length - 1
    71. LstStr(i).Add(Rand.Next(AlphaString.Length))
    72. Next
    73. Next
    74. End Sub
    75. Private Sub AlphaStrings()
    76. Dim res As New StringBuilder(62)
    77. Dim z() As Byte = {48, 57, 65, 90, 97, 122}
    78. For i As Int32 = 0 To z.Length - 1 Step 2
    79. For j As Byte = z(i) To z(i + 1)
    80. res.Append(Convert.ToChar(j))
    81. Next
    82. Next
    83. AlphaString = res.ToString
    84. End Sub
    85. End Module

    cs

    C#-Quellcode

    1. using System;
    2. using System.Linq;
    3. using System.Text;
    4. using System.Diagnostics;
    5. using System.Collections.Generic;
    6. namespace StringVsStringbuilderCs
    7. {
    8. public class Program
    9. {
    10. private static string AlphaString;
    11. private static List<List<int>> LstStr;
    12. private static Random Rand = new Random();
    13. public static void Main(string[] args)
    14. {
    15. int size = 1000;
    16. int length = 1234;
    17. AlphaStrings();
    18. RngCharNumber(size, length);
    19. // Der eigentliche Test
    20. Console.WriteLine("CSharp-Test");
    21. Console.WriteLine("***********");
    22. Console.WriteLine($"ListOfT.Length = {size}");
    23. Console.WriteLine($"String.Length = {length}");
    24. Console.WriteLine();
    25. TestString();
    26. TestStringBuilder();
    27. TestStringJoin();
    28. Console.ReadLine();
    29. }
    30. private static void TestString()
    31. {
    32. string str, s;
    33. var sw = Stopwatch.StartNew();
    34. str = string.Empty;
    35. for (int i = 0; i < LstStr.Count; i++)
    36. {
    37. s = string.Empty;
    38. for (int j = 0; j < LstStr[i].Count; j++)
    39. s += AlphaString[LstStr[i][j]];
    40. str += s;
    41. }
    42. Console.WriteLine($"{nameof(TestString)}: {sw.ElapsedMilliseconds}ms");
    43. GC.Collect();
    44. }
    45. private static void TestStringBuilder()
    46. {
    47. StringBuilder str, s;
    48. var sw = Stopwatch.StartNew();
    49. str = new StringBuilder();
    50. for (int i = 0; i < LstStr.Count; i++)
    51. {
    52. s = new StringBuilder();
    53. for (int j = 0; j < LstStr[i].Count; j++)
    54. s.Append(AlphaString[LstStr[i][j]]);
    55. str.Append(s);
    56. }
    57. Console.WriteLine($"{nameof(TestStringBuilder)}: {sw.ElapsedMilliseconds}ms");
    58. GC.Collect();
    59. }
    60. private static void TestStringJoin()
    61. {
    62. var sw = Stopwatch.StartNew();
    63. var lstr = new List<List<char>>(LstStr.Count);
    64. for (int i = 0; i < LstStr.Count; i++)
    65. {
    66. lstr.Add(new List<char>(LstStr[i].Count));
    67. for (int j = 0; j < LstStr[i].Count; j++)
    68. lstr[i].Add(AlphaString[LstStr[i][j]]);
    69. }
    70. var stmp = lstr.Select(s => string.Join("", s)).ToArray();
    71. string str = string.Join("", stmp);
    72. Console.WriteLine($"{nameof(TestStringJoin)}: {sw.ElapsedMilliseconds}ms");
    73. GC.Collect();
    74. }
    75. private static void RngCharNumber(int size, int length)
    76. {
    77. LstStr = new List<List<int>>(size);
    78. for (int i = 0, loopTo = size - 1; i <= loopTo; i++)
    79. {
    80. LstStr.Add(new List<int>(length));
    81. for (int j = 0, loopTo1 = length - 1; j <= loopTo1; j++)
    82. LstStr[i].Add(Rand.Next(AlphaString.Length));
    83. }
    84. }
    85. private static void AlphaStrings()
    86. {
    87. var res = new StringBuilder(62);
    88. var z = new byte[] { 48, 57, 65, 90, 97, 122 };
    89. for (int i = 0, loopTo = z.Length - 1; i <= loopTo; i += 2)
    90. {
    91. for (byte j = z[i], loopTo1 = z[i + 1]; j <= loopTo1; j++)
    92. res.Append(Convert.ToChar(j));
    93. }
    94. AlphaString = res.ToString();
    95. }
    96. }
    97. }

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „exc-jdbi“ ()

    Ich hab mich mal rangesetzt, und ebenfalls einen Benchmark gebastelt. Hier mal der Code, der in .NET Core 3.1 geschrieben wurde:
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Diagnostics;
    4. using System.Linq;
    5. using System.Text;
    6. namespace StringBuilderTest
    7. {
    8. class Program
    9. {
    10. static Random rd = new Random(42);
    11. static void Main(string[] args)
    12. {
    13. int runs = 10;
    14. int iterations = 10000;
    15. int stringLength = 123;
    16. for (int r = 0; r < runs; r++)
    17. {
    18. List<long> ticksCreateRandom = new List<long>();
    19. List<long> ticksAddList = new List<long>();
    20. List<long> ticksAddBuilder = new List<long>();
    21. List<long> ticksJoinList = new List<long>();
    22. List<long> ticksToStringBuilder = new List<long>();
    23. List<string> list = new List<string>();
    24. StringBuilder builder = new StringBuilder();
    25. Stopwatch sw = new Stopwatch();
    26. for (int i = 0; i < iterations; i++)
    27. {
    28. sw.Restart();
    29. string randomString = CreatePassword(stringLength);
    30. sw.Stop();
    31. ticksCreateRandom.Add(sw.ElapsedTicks);
    32. sw.Restart();
    33. list.Add(randomString);
    34. sw.Stop();
    35. ticksAddList.Add(sw.ElapsedTicks);
    36. sw.Restart();
    37. builder.Append(randomString);
    38. sw.Stop();
    39. ticksAddBuilder.Add(sw.ElapsedTicks);
    40. }
    41. for (int i = 0; i < iterations; i++)
    42. {
    43. sw.Restart();
    44. string resultJoin = string.Join("", list);
    45. sw.Stop();
    46. ticksJoinList.Add(sw.ElapsedTicks);
    47. sw.Restart();
    48. string resultBuild = builder.ToString();
    49. sw.Stop();
    50. ticksToStringBuilder.Add(sw.ElapsedTicks);
    51. }
    52. long ticksListTotal = ticksCreateRandom.Sum() + ticksAddList.Sum() + ticksJoinList.Sum();
    53. long ticksBuilderTotal = ticksCreateRandom.Sum() + ticksAddBuilder.Sum() + ticksToStringBuilder.Sum();
    54. Console.WriteLine($"iterations : {iterations}");
    55. Console.WriteLine($"string length: {stringLength}");
    56. Console.WriteLine($"{"Creation Time",-21} - Min:{ticksCreateRandom.Min(),10} ticks | Avg:{ticksCreateRandom.Average(),10:f3} ticks | Max:{ticksCreateRandom.Max(),10} ticks | Total:{ticksCreateRandom.Sum(),10} ticks");
    57. Console.WriteLine($"{"Add List Time",-21} - Min:{ticksAddList.Min(),10} ticks | Avg:{ticksAddList.Average(),10:f3} ticks | Max:{ticksAddList.Max(),10} ticks | Total:{ticksAddList.Sum(),10} ticks");
    58. Console.WriteLine($"{"Add Builder Time",-21} - Min:{ticksAddBuilder.Min(),10} ticks | Avg:{ticksAddBuilder.Average(),10:f3} ticks | Max:{ticksAddBuilder.Max(),10} ticks | Total:{ticksAddBuilder.Sum(),10} ticks");
    59. Console.WriteLine($"{"Join List Time",-21} - Min:{ticksJoinList.Min(),10} ticks | Avg:{ticksJoinList.Average(),10:f3} ticks | Max:{ticksJoinList.Max(),10} ticks | Total:{ticksJoinList.Sum(),10} ticks");
    60. Console.WriteLine($"{"ToString Builder Time",-21} - Min:{ticksToStringBuilder.Min(),10} ticks | Avg:{ticksToStringBuilder.Average(),10:f3} ticks | Max:{ticksToStringBuilder.Max(),10} ticks | Total:{ticksToStringBuilder.Sum(),10} ticks");
    61. Console.WriteLine();
    62. Console.WriteLine($"{"Total List Time",-21}: {ticksListTotal,15} ticks | {ticksListTotal / (Stopwatch.Frequency / 1000.0),14:f3} ms | {ticksListTotal / (Stopwatch.Frequency / 1.0),10:f3} s");
    63. Console.WriteLine($"{"Total Builder Time",-21}: {ticksBuilderTotal,15} ticks | { ticksBuilderTotal / (Stopwatch.Frequency / 1000.0),14:f3} ms | {ticksBuilderTotal / (Stopwatch.Frequency / 1.0),10:f3} s");
    64. Console.WriteLine();
    65. Console.WriteLine();
    66. }
    67. Console.ReadKey(true);
    68. }
    69. public static string CreatePassword(int passwordLength, string AllChar = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789!@$?_-[](){}=§$%&/<>|;,:._-#'*+~€`´\\")
    70. {
    71. string allowedChars = AllChar;
    72. char[] chars = new char[passwordLength];
    73. for (int i = 0; i < passwordLength; i++)
    74. {
    75. chars[i] = allowedChars[rd.Next(0, allowedChars.Length)];
    76. }
    77. return new string(chars);
    78. }
    79. }
    80. }

    Und hier meine Ergebnisse von Links nach rechts:
    • Zuerst mal, die Parameter von @exc-jdbi
    • Weniger Iterationen, größerer String:
    • Noch weniger Iterationen, noch größerer String
    • Mehr iterationen, und kleinerer String
    • Und zum Schluss selbe Iterationen, String länge 1


    Bedeutet, je mehr Strings, aneinander gefügt werden, und je kleiner die Strings, desto schneller ist der StringBuilder gegenüber der String.Join Operation.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „EaranMaleasi“ ()

    ErfinderDesRades schrieb:

    dann nehme ich eine List(Of Object), pack da meine Daten in richtiger Reihenfolge rein, und verwandle es am Ende in einen STring, mittels String.Join() oder String.Concat()

    Interessant. Diesen Ansatz muss ich bei Gelegenheit mal testen. :)


    ErfinderDesRades schrieb:


    Wobei die Geschwindigkeit ja eh egal ist.

    Warum?


    mrMo schrieb:


    Was programmierst du das die Nutzung eines StringBuilder spürbare Performance Vorteile bringt?

    Ich? Einen Z80 Disassembler. Aktuell liegen die einzelnen Zeilen in einer ArrayList. Die wird ausgelesen und die einzelnen Teile per StringBuilder zusammen gesetzt.
    Der SB-String wird dann mit .ToString in einen normalen String umgewandelt und in einer RichTextBox angezeigt. Das ist aktuell mehr als schnell genug. Aber das
    Projekt wird langsam umfangreicher und da denke ich hin und wieder an Verbesserungsmöglichkeiten. Durch diesen Thread habe ich gute Hinweise bekommen. :)
    Aktuelles Projekt: Z80 Disassembler für Schneider/Amstrad CPC :love:

    oobdoo schrieb:

    ErfinderDesRades schrieb:

    Wobei die Geschwindigkeit ja eh egal ist.
    Warum?
    Es sollte eine Binsenweisheit sein, aber sie setzt und setzt und setzt sich nicht durch:

    Rules of Optimization! (bitte folge dem Link)



    Oder anderer Ansatz:

    oobdoo schrieb:

    ErfinderDesRades schrieb:

    Wobei die Geschwindigkeit ja eh egal ist.
    Warum?
    Weil es nunmal so ist.
    Bring mir ein Beispiel aus der Praxis, wo es die String-Performance war, die irgendeinen Vorgang spürbar behindert hätte.
    Und wenn du tatsächlich eines finden solltest, dann kann ich dir 10000 Beispiele aufzeigen, wo dem nicht so ist.
    Also zu 99.99% habe ich recht, und da erlaube ich mir aufzurunden, vor allem, wo's grad konkret drum geht, wegen der 0,01% nu anzufangen, Stringbuilder flächendeckend einzusetzen - und so Code auf breiter Front unleserlicher zu machen.
    @ErfinderDesRades:
    Das jetzt nicht, um gegen dich zu argumentieren, sondern weil ich die Geschichte sehr amüsant fand.
    Ein Freund hatte ein Programm, das Daten aus 'ner SQL-Datenbank holt und als XML exportiert. Hat pro Job ca. eine Stunde gedauert und ca. 20MB XML produziert.
    Er hat die Stringkonkatenationen (und ja, es ist in .NET unsinnig, XML durch Stringkonkatenation zu generieren) durch einen StringBuilder ersetzt und das hat die Jobs auf 3 Sekunden reduziert.
    Also... wenn man wirklich wirklich viele Strings verknüpft, hilft ein StringBuilder.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    ErfinderDesRades schrieb:

    Stringbuilder flächendeckend einzusetzen - und so Code auf breiter Front unleserlicher zu machen.
    Schau dir doch mal meinen Code oben an. Was genau ist jetzt am StringBuilder unleserlicher? Ob ich etliche Strings in ne List<string> Packe, und dann eine Methode benutze um daraus einen String zu machen, oder etliche Strings in einen StringBuilder packe, und dann eine Methode benutze um daraus einen String zu machen, kommt aufs selbe raus. Letzteres ist jedoch, sofern ich mir in meinem Benchmark keinen groben Schnitzer erlaubt habe, schneller. Wie viel schneller? sagen wir mal zwischen 1.5x - 10x schneller in der Praxis. Und wenn jemand wirklich Tag ein, Tag aus einzelne Zeichen zusammen fügt, der wird sich darüber freuen, dass der StringBuilder 57x schneller ist, als dein Vorschlag.

    Demnach ist diese Einschätzung:

    ErfinderDesRades schrieb:

    Dassis ... vermutlich sogar schneller.
    auf absolut Nichts basiert.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „EaranMaleasi“ ()

    ok - petaod hat ein Beispiel gefunden, wo Stringbuilder zur Optimierung nutzbar war.
    Und nun muss ich meine 10000 Beispiele bringen, wo's wie gesagt vollkommen irrelevant ist.
    Sorry - das ist mir zu mühsam, jetzt jede Verwendung von String in meinem kurzen Programmiererleben hier aufzulisten, und würde evtl. auch die Forum-Software überlasten.
    Daher Bitte: Glaubts mir einfach.



    @EaranMaleasi: Strings kann man auch ohne Stringbuilder verketten - das ist definitiv weniger Code, und somit leserlicher.
    Und zu 99% tut man das auch.
    Also einfach 2 Strings verketten ist ein Befehl, und mit StringBuilder sinds 3-4 Befehle. Wenn du da flächendeckend Bock drauf hast (so verstand ich post#1, dasser darauf abzielte) - bitte.
    Wie gesagt:
    Es sollte eine Binsenweisheit sein, aber sie setzt und setzt und setzt sich nicht durch:
    Rules of Optimization!
    (bitte folge dem Link)
    Und geht mir schon wieder auf die Nerven, dass ich da jetzt wieder am herum-argumentieren bin - für etwas, das doch total banal ist.

    Oder anners: Stringbuilder ist ein Instrument (nicht unbedingt das beste!), wenn man String-Concatenationen aus Performance-Gründen optimieren muss.
    Nur muss man das so gut wie nie.



    EaranMaleasi schrieb:

    ErfinderDesRades schrieb:

    Dassis ... vermutlich sogar schneller.
    auf absolut Nichts basiert.
    wie dort ja steht: eine Vermutung. Und ist durchaus basiert! Nämlich auf dem Wissen, dass String.Concat - anders als eine sukzessive StringVerkettung - nicht für jedes Element den String umkopieren muss.
    Ich mach da jetzt aber keine Tests, weil das wie gesagt vollkommen irrelevant ist.
    Und wenn String.Concat nicht schneller ist, sondern mw. 100mal langsamer als StringBuilder.Append - was ich stark bezweifel - wen würde es interessieren?
    Meinen Listen-Trick mache ich nicht für performance, sondern um meine String-Frickeleien möglichst lesbar zu halten - und da ist Stringbuilder eben meist schlecht geeignet.
    Guck - hier - zB die Methode CustomFill aus Dataset->Db :

    VB.NET-Quellcode

    1. Public Sub CustomFill(table As DataTable, sqlAfterFrom As String, ParamArray args() As Object)
    2. Dim childrenFinder = New TopologicSort(_RankedTables, False)
    3. For Each tb In childrenFinder.GetRankedTables(table) : tb.Clear() : Next
    4. Using adp = DirectCast(DirectCast(_Adapters(table), ICloneable).Clone, SqlCeDataAdapter)
    5. Dim cmd = adp.SelectCommand
    6. Dim splits = sqlAfterFrom.Split("?"c)
    7. For i = 0 To args.Length - 1
    8. Dim name = "@p" & i
    9. cmd.Parameters.AddWithValue(name, args(i))
    10. splits(i) = String.Concat(splits(i), " ", name, " ")
    11. Next
    12. cmd.CommandText &= " " & String.Concat(splits)
    13. _Con.Open()
    14. adp.Fill(table)
    15. _Con.Close()
    16. End Using
    17. End Sub
    Kannste gerne auf Stringbuilder umstellen, und dann performance-tests machen.
    Und wenn die String-Operationen darin dann 50-mal schneller sind - was ich bezweifel - es wird immer noch irrelevant sein.
    Aber relevant wird sein, ob das Stringbuilder-Gefummel ebenso leserlich ist wie es jetzt ist.

    Hier noch weitere Beispiele, wie ich String.Join/Concat einem StringBuilder vorziehe.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Text.RegularExpressions
    2. Public Class NoInject
    3. Private Class SqlInjectionException : Inherits Exception
    4. End Class
    5. Private Shared _SqlWhiteList As String = "ALL AND ANY AS ASC BEGIN BETWEEN BY CASE CONTAINS CONVERT DEFAULT DESC DISTINCT ELSE EXCEPT EXISTS FOR FROM FULL GROUP HAVING IF IIF IN INNER INTERSECT INTO IS JOIN LEFT LIKE NOT NULL NULLIF ON OR ORDER OUTER OVER PERCENT RIGHT SELECT SOME THEN TOP TRY_CONVERT TSEQUAL UNION WHEN WHERE WITH"
    6. Private Shared _ForbiddenSyntax As String() = "@ -- ; /* */".Split
    7. Private Shared _rgxWordBorder As New Regex("\b")
    8. Private Shared _rgxBracktet As New Regex("[\[\]]")
    9. ''' <summary>Converts sql to a sql-injection-safe version, or reject it by returning null</summary>
    10. Public Shared Function MakeSafe(sql As String) As String
    11. If sql Is Nothing Then Return Nothing
    12. Try
    13. Dim splits = sql.Split("'"c)
    14. If (splits.Length And 1) <> 1 Then Throw New SqlInjectionException ' an even splitCount indicates not-closed Quotes
    15. For i = 0 To splits.Length - 1 Step 2
    16. splits(i) = ProcessUnquotedPart(splits(i))
    17. Next
    18. Return String.Join("'"c, splits)
    19. Catch ex As SqlInjectionException
    20. Return Nothing
    21. End Try
    22. End Function
    23. Private Shared Function ProcessUnquotedPart(sNoQuotes As String) As String
    24. ValidateBrackets(sNoQuotes)
    25. Dim splits = _rgxBracktet.Split(sNoQuotes)
    26. For i = 0 To splits.Length - 1 Step 2 ' process nonBracket parts
    27. splits(i) = ProcessNonBracketPart(splits(i))
    28. Next
    29. If splits.Length = 1 Then Return splits(0) ' only one splt: sNoQuotes contained no Bracket
    30. Dim innerSegments = String.Join("][", splits.Skip(1).Take(splits.Length - 2))
    31. Return $"{ splits(0)}[{innerSegments}]{ splits.Last}" ' re-concat splits and restore Brackets
    32. End Function
    33. Private Shared Function ProcessNonBracketPart(sNoBracket As String) As String
    34. 'Ausserhalb von [], ''
    35. 'regeln: a) die in _ForbiddenSyntax geführten Zeichenfolgen sind nicht erlaubt
    36. 'b) nicht in _SqlWhiteList aufgeführte Worte werden mit [] gequotet
    37. If sNoBracket.Split(_ForbiddenSyntax, StringSplitOptions.None).Length > 1 Then Throw New SqlInjectionException
    38. Dim splits = _rgxWordBorder.Split(sNoBracket) ' _rgxWordBorder splittet an Wortgrenzen - verschluckt dabei keine Zeichen
    39. For i = 1 To splits.Length - 1 Step 2 ' process words
    40. If _SqlWhiteList.IndexOf(splits(i)) < 0 Then splits(i) = $"[{splits(i)}]" ' quote non-whitelist-words with []
    41. Next
    42. Return String.Concat(splits)
    43. End Function
    44. Private Shared Sub ValidateBrackets(sNoQuotes As String)
    45. 'Brackets: [] in Sql sind nicht schachtelbar.
    46. 'daher folgende regeln: erste Bracket muss öffnend sein, letzte schliessend. Dazwischen alternierend
    47. Dim prev = "]"
    48. For Each mt As Match In _rgxBracktet.Matches(sNoQuotes)
    49. If mt.Value = prev Then Throw New SqlInjectionException 'keine Verschachtelung erlaubt
    50. prev = mt.Value
    51. Next
    52. If prev <> "]" Then Throw New SqlInjectionException 'letzte muss geschlossen sein
    53. End Sub
    54. End Class
    Das ist eine Analyse-Komponente, um Sql-Injection abzuwehren - also ganz typischer Fall für verhältnismässig komplexe String-Verarbeitung - siehe Gegen SqlInjection ( + Tests )
    Und auch hier zeigt sich, dass die Performance irrelevant ist, sondern Lesbarkeit hinzukriegen ist die Herausforderung - weils eben ein schwieriges Thema ist.
    Wie gesagt:
    • Bei banalen Concatenationen ist StringBuilder Quatsch (zB dim truth = "StringBuilder" & "wäre" & "hier" & "Quatsch")
    • Und auch bei anspruchsvollen StringVerarbeitungen ist der Performance-Gewinn gradezu grotesk irrelevant.
      ABer SB ist bei anspruchsvollen StringVerarbeitungen in Punkto Lesbarkeit meist deutlich weniger geeignet als andere Mittel.

    Dieser Beitrag wurde bereits 20 mal editiert, zuletzt von „ErfinderDesRades“ ()

    ErfinderDesRades schrieb:

    Also einfach 2 Strings verketten ist eine Operation

    In dem Fall ist der StringBuilder, als auch die List absoluter Schwachsinn, und wenn deine erste Aussage, die ich zitiert habe, sich ausschließlich darauf bezog, dann habe ich das falsch verstanden.
    Auch was das voreilige Optimieren angeht, sehe ich das genau gleich. Keine Not also die Textform des Herumbrüllens zu verwenden.