Database
 sql >> Datenbank >  >> RDS >> Database

Ist der String-Operator „+“ so einfach?

Einführung

Ein String-Datentyp ist neben numerischen (int, long, double) und logischen (booleschen) Datentypen einer der grundlegenden Datentypen. Sie können sich kaum ein nützliches Programm vorstellen, das diesen Typ nicht verwendet.

Auf der .NET-Plattform wird der String-Typ als unveränderliche String-Klasse dargestellt. Außerdem ist es stark in die CLR-Umgebung integriert und wird auch vom C#-Compiler unterstützt.

Dieser Artikel ist der Verkettung gewidmet – einer Operation, die für Strings genauso oft durchgeführt wird wie die Additionsoperation für Zahlen. Sie denken vielleicht:„Was soll man dazu sagen?“, schließlich kennen wir alle den String-Operator „+“, aber wie sich herausstellte, hat er seine eigenen Macken.

Sprachangabe für String-Operator „+“

Die C#-Sprachspezifikation bietet drei Überladungen für den Zeichenfolgenoperator „+“:

string operator + (string x, string y)

string operator + (string x, object y)

string operator + (object x, string y)

Wenn einer der Operanden der Zeichenfolgenverkettung NULL ist, wird die leere Zeichenfolge eingefügt. Andernfalls wird jedes Argument, das kein String ist, als String dargestellt, indem die virtuelle ToString-Methode aufgerufen wird. Wenn die ToString-Methode NULL zurückgibt, wird eine leere Zeichenfolge eingefügt. Es sollte beachtet werden, dass diese Operation gemäß der Spezifikation niemals NULL zurückgeben sollte.

Die Beschreibung des Operators ist klar genug, aber wenn wir uns die Implementierung der String-Klasse ansehen, finden wir eine klare Definition von nur zwei Operatoren „==“ und „!=“. Es stellt sich eine vernünftige Frage:Was passiert hinter den Kulissen der Zeichenfolgenverkettung? Wie geht der Compiler mit dem String-Operator „+“ um?

Die Antwort auf diese Frage stellte sich als gar nicht so schwierig heraus. Schauen wir uns die statische String.Concat-Methode genauer an. Die String.Concat-Methode verbindet eine oder mehrere Instanzen der String-Klasse oder Ansichten als String-Werte einer oder mehrerer Instanzen von Object. Es gibt folgende Überladungen dieser Methode:

public static String Concat (String str0, String str1)

public static String Concat (String str0, String str1, String str2)

public static String Concat (String str0, String str1, String str2, String str3)

public static String Concat (params String[] values)

public static String Concat (IEnumerable <String> values)



public static String Concat (Object arg0)

public static String Concat (Object arg0, Object arg1)

public static String Concat (Object arg0, Object arg1, Object arg2)

public static String Concat (Object arg0, Object arg1, Object arg2, Object arg3, __arglist)



public static String Concat <T> (IEnumerable <T> values)

Einzelheiten

Angenommen, wir haben den folgenden Ausdruck s =a + b, wobei a und b Strings sind. Der Compiler wandelt es in einen Aufruf einer statischen Concat-Methode um, d. h.

s = string.Concat (a, b)

Die Zeichenfolgenverkettungsoperation ist, wie jede andere Additionsoperation in der C#-Sprache, linksassoziativ.

Mit zwei Zeilen ist alles klar, aber was ist, wenn es mehr Zeilen gibt? Der Ausdruck s =a + b + c könnte angesichts der Linksassoziativität der Operation ersetzt werden durch:

s = string.Concat(string.Concat (a, b), c)

Angesichts der Überladung, die drei Argumente erfordert, wird es jedoch konvertiert in:

s = string.Concat (a, b, c)

Ähnlich verhält es sich mit der Verkettung von vier Strings. Um 5 oder mehr Strings zu verketten, haben wir die string.Concat-Überladung (params string[]), daher ist es notwendig, den Overhead zu berücksichtigen, der mit der Speicherzuweisung für ein Array verbunden ist.

Es sollte auch beachtet werden, dass der String-Verkettungsoperator vollständig assoziativ ist :Es spielt keine Rolle, in welcher Reihenfolge wir Strings verketten, daher soll der Ausdruck s =a + (b + c) trotz der explizit angegebenen Priorität der Verkettungsausführung wie folgt verarbeitet werden

s = (a + b) + c = string.Concat (a, b, c)

statt wie erwartet:

s = string.Concat (a, string.Concat (b, c))

Fassung des Vorhergehenden:Die String-Verkettungsoperation wird immer von links nach rechts dargestellt und ruft die statische String.Concat-Methode auf.

Optimieren des Compilers für wörtliche Zeichenfolgen

Der C#-Compiler hat Optimierungen in Bezug auf wörtliche Zeichenfolgen. Zum Beispiel ist der Ausdruck s =„a“ + „b“ + c angesichts der Linksassoziativität des „+“-Operators äquivalent zu s =(„a“ + „b“) + c wird umgewandelt in

s = string.Concat ("ab", c)

Der Ausdruck s =c + „a“ + „b“ wird trotz der Linksassoziativität der Verkettungsoperation (s =(c + „a“) ​​+ „b“) in

umgewandelt
s = string.Concat (c, "ab")

Im Allgemeinen spielt die Position der Literale keine Rolle, der Compiler verkettet alles, was er kann, und versucht erst dann, eine geeignete Überladung der Concat-Methode auszuwählen. Der Ausdruck s =a + „b“ + „c“ + d wird umgewandelt in

s = string.Concat (a, "bc", d)

Optimierungen im Zusammenhang mit leeren und NULL-Strings sollten ebenfalls erwähnt werden. Der Compiler weiß, dass das Hinzufügen eines leeren Strings das Ergebnis der Verkettung nicht beeinflusst, daher wird der Ausdruck s =a + „“ + b in

umgewandelt
s = string.Concat (a, b),

statt wie erwartet

s = string.Concat (a, "", b)

In ähnlicher Weise haben wir mit dem konstanten String, dessen Wert NULL ist:

const string nullStr = null;

s = a + nullStr + b;

wird umgewandelt in

s = string.Concat (a, b)

Der Ausdruck s =a + nullStr wird umgewandelt in s =a ?? „“, wenn a ein String ist, und der Aufruf der Methode string.Concat (a), wenn a kein String ist, z. B. s =17 + nullStr, wird in s =string.Concat (17) konvertiert. .

Ein interessantes Feature im Zusammenhang mit der Optimierung der Literalverarbeitung und der Linksassoziativität des Zeichenfolgenoperators „+“.

Betrachten wir den Ausdruck:

var s1 = 17 + 17 + "abc";

unter Berücksichtigung der Linksassoziativität ist es äquivalent zu

var s1 = (17 + 17) + "abc"; // сalling the string.Concat method (34, "abc")

Als Ergebnis werden zur Kompilierzeit die Ziffern hinzugefügt, sodass das Ergebnis 34abc ist.

Andererseits der Ausdruck

var s2 = "abc" + 17 + 17;

entspricht

var s2 = ( "abc" + 17) + 17; // calling the string.Concat method ("abc", 17, 17)

das Ergebnis ist abc1717.

Also, los geht's, derselbe Verkettungsoperator führt zu unterschiedlichen Ergebnissen.

String.Concat VS StringBuilder.Append

Es ist notwendig, ein paar Worte zu diesem Vergleich zu sagen. Betrachten wir den folgenden Code:

string name = "Timur";

string surname = "Guev";

string patronymic = "Ahsarbecovich";

string fio = surname + name + patronymic;

Es kann mit StringBuilder durch den Code ersetzt werden:

var sb = new StringBuilder ();

sb.Append (surname);

sb.Append (name);

sb.Append (patronymic);

string fio = sb.ToString ();

In diesem Fall werden wir jedoch kaum Vorteile aus der Verwendung von StringBuilder ziehen. Abgesehen davon, dass der Code weniger lesbar geworden ist, ist er mehr oder weniger effektiv geworden, da die Implementierung der Concat-Methode die Länge des resultierenden Strings berechnet und Speicher nur einmal allokiert, im Gegensatz zu StringBuilder, der nichts über die Länge weiß der resultierenden Zeichenfolge.

Implementierung der Concat-Methode für 3 Strings:

public static string Concat (string str0, string str1, string str2)

{

if (str0 == null && str1 == null && str2 == null)

return string.Empty;

if (str0 == null)

str0 = string.Empty;

if (str1 == null)

str1 = string.Empty;

if (str2 == null)

str2 = string.Empty;

string dest = string.FastAllocateString (str0.Length + str1.Length + str2.Length); // Allocate memory for strings

string.FillStringChecked (dest, 0, str0); /

string.FillStringChecked (dest, str0.Length, str1);

string.FillStringChecked (dest, str0.Length + str1.Length, str2);

return dest;

}

Operator „+“ in Java

Ein paar Worte zum String-Operator „+“ in Java. Ich programmiere zwar nicht in Java, interessiere mich aber dafür, wie es dort funktioniert. Der Java-Compiler optimiert den „+“-Operator so, dass er die StringBuilder-Klasse verwendet und die Append-Methode aufruft.

Der vorherige Code wird umgewandelt in

String fio = new StringBuilder(String.valueOf(surname)).append(name).append (patronymic).ToString()

Es ist erwähnenswert, dass sie eine solche Optimierung in C# absichtlich abgelehnt haben, Eric Lippert hat einen Beitrag zu diesem Thema. Der Punkt ist, dass eine solche Optimierung nicht die Optimierung als solche ist, sondern ein Umschreiben des Codes. Außerdem sind die Entwickler der C#-Sprache der Meinung, dass Entwickler mit den Aspekten der Arbeit mit der String-Klasse vertraut sein und gegebenenfalls zu StringBuilder wechseln sollten.

Übrigens war Eric Lippert derjenige, der an der Optimierung des C#-Compilers im Zusammenhang mit der Verkettung von Strings gearbeitet hat.

Schlussfolgerung

Vielleicht mag es auf den ersten Blick seltsam erscheinen, dass die String-Klasse den Operator „+“ nicht definiert, bis wir über die Optimierungsfähigkeit des Compilers in Bezug auf die Sichtbarkeit eines größeren Codefragments nachdenken. Wenn beispielsweise der „+“-Operator in der String-Klasse definiert wurde, würde der Ausdruck s =a + b + c + d zur Erstellung von zwei Zwischenstrings führen, ein einziger Aufruf des Strings. Concat (a, b, c, d)-Methode ermöglicht eine effektivere Durchführung der Verkettung.