sql >> データベース >  >> RDS >> Database

文字列演算子「+」はとても単純ですか?

    はじめに

    文字列データ型は、数値(int、long、double)および論理(ブール)型と並んで、基本的なデータ型の1つです。このタイプを利用しない有用なプログラムを少なくとも1つは想像できません。

    .NETプラットフォームでは、文字列型は不変の文字列クラスとして表示されます。さらに、CLR環境に強力に統合されており、C#コンパイラでもサポートされています。

    この記事では、連結について説明します。これは、数字の加算操作と同じくらい頻繁に文字列に対して実行される操作です。 「何を言うべきか」と思うかもしれませんが、結局のところ、文字列演算子「+」については誰もが知っていますが、結局のところ、独自の癖があります。

    文字列演算子「+」の言語仕様

    C#言語仕様では、文字列演算子「+」に3つのオーバーロードが提供されています。

    string operator + (string x, string y)
    
    string operator + (string x, object y)
    
    string operator + (object x, string y)

    文字列連結のオペランドの1つがNULLの場合、空の文字列が挿入されます。それ以外の場合、文字列ではない引数は、ToString仮想メソッドを呼び出すことによって文字列として表されます。 ToStringメソッドがNULLを返す場合、空の文字列が挿入されます。仕様によれば、この操作は決してNULLを返さないことに注意してください。

    演算子の説明は十分に明確ですが、Stringクラスの実装を見ると、2つの演算子「==」と「!=」のみの明確な定義が見つかります。合理的な疑問が生じます:文字列の連結の舞台裏で何が起こるのでしょうか?コンパイラは文字列演算子「+」をどのように処理しますか?

    この質問への答えは、それほど難しくないことがわかりました。静的なString.Concatメソッドを詳しく見てみましょう。 String.Concatメソッドは、Stringクラスの1つ以上のインスタンスを結合するか、Objectの1つ以上のインスタンスのString値として表示します。このメソッドには次のオーバーロードがあります:

    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)

    詳細

    次の式s=a + bがあるとします。ここで、aとbは文字列です。コンパイラはそれをConcat静的メソッドの呼び出しに変換します。つまり

    s = string.Concat (a, b)

    C#言語の他の加算操作と同様に、文字列連結操作は左連想です。

    2行ですべてが明確ですが、さらに行がある場合はどうなりますか?操作の左結合性を考えると、式s =a + b + cは、次のように置き換えることができます。

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

    ただし、3つの引数を取るオーバーロードを考えると、次のように変換されます。

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

    同様の状況は、4つのストリングの連結です。 5つ以上の文字列を連結するには、string.Concatオーバーロード(params string [])があるため、配列のメモリ割り当てに関連するオーバーヘッドを考慮する必要があります。

    文字列連結演算子は完全に結合的であることにも注意してください。 :文字列を連結する順序は重要ではないため、連結実行の優先順位が明示的に示されているにもかかわらず、式s =a +(b + c)は次のように処理されます。

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

    期待の代わりに:

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

    したがって、前述のことを要約すると、文字列の連結操作は常に左から右に表され、静的なString.Concatメソッドを呼び出します。

    リテラル文字列用のコンパイラの最適化

    C#コンパイラには、リテラル文字列に関連する最適化があります。たとえば、式s =“ a” +“ b” + cは、“ +”演算子の左結合性が与えられた場合、s =(“ a” +“ b”)+cは

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

    式s=c +“ a” +“ b”は、連結の操作の左結合法則(s =(c +“ a”)+“ b”)にもかかわらず、

    に変換されます。
    s = string.Concat (c, "ab")

    一般に、リテラルの位置は重要ではなく、コンパイラーは可能な限りすべてを連結してから、Concatメソッドの適切なオーバーロードを選択しようとします。式s=a +“ b” +“ c”+dはに変換されます

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

    空の文字列とNULL文字列に関連する最適化についても言及する必要があります。コンパイラは、空のstingを追加しても連結の結果に影響しないことを認識しているため、式s =a +“”+bはに変換されます

    s = string.Concat (a, b),

    期待される代わりに

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

    同様に、値がNULLであるconst文字列を使用すると、次のようになります。

    const string nullStr = null;
    
    s = a + nullStr + b;

    に変換されます

    s = string.Concat (a, b)

    式s=a+nullStrはs=a??に変換されます。 「」、aが文字列の場合、およびstring.Concatメソッド(a)の呼び出し、aが文字列でない場合、たとえばs =17 + nullStrの場合、s =string.Concat(17)に変換されます。 。

    リテラル処理の最適化と文字列演算子「+」の左結合性に関連する興味深い機能。

    式を考えてみましょう:

    var s1 = 17 + 17 + "abc";

    左結合性を考慮すると、

    と同等です。
    var s1 = (17 + 17) + "abc"; // сalling the string.Concat method (34, "abc")

    その結果、コンパイル時に数字が追加され、結果は34abcになります。

    一方、式

    var s2 = "abc" + 17 + 17;

    と同等です
    var s2 = ( "abc" + 17) + 17; // calling the string.Concat method ("abc", 17, 17)

    結果はabc1717になります。

    つまり、同じ連結演算子を使用すると、結果が異なります。

    String.Concat VS StringBuilder.Append

    この比較について一言言う必要があります。次のコードを考えてみましょう:

    string name = "Timur";
    
    string surname = "Guev";
    
    string patronymic = "Ahsarbecovich";
    
    string fio = surname + name + patronymic;

    StringBuilderを使用してコードに置き換えることができます:

    var sb = new StringBuilder ();
    
    sb.Append (surname);
    
    sb.Append (name);
    
    sb.Append (patronymic);
    
    string fio = sb.ToString ();

    ただし、この場合、StringBuilderを使用してもメリットはほとんどありません。コードが読みにくくなったという事実は別として、Concatメソッドの実装は結果の文字列の長さを計算し、メモリを1回だけ割り当てるため、長さについて何も知らないStringBuilderとは対照的に、コードは多かれ少なかれ効果的になりました。結果の文字列の。

    3つの文字列に対するConcatメソッドの実装:

    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;
    
    }

    Javaの演算子「+」

    Javaの文字列演算子「+」に関するいくつかの単語。私はJavaでプログラミングしていませんが、Javaでどのように機能するかに興味があります。 Javaコンパイラは、「+」演算子を最適化して、StringBuilderクラスを使用し、appendメソッドを呼び出します。

    前のコードはに変換されます

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

    彼らがC#でのそのような最適化を意図的に拒否したことは注目に値します、EricLippertはこのトピックに関する投稿をしています。重要なのは、そのような最適化はそれ自体の最適化ではなく、コードの書き換えであるということです。さらに、C#言語の作成者は、開発者はStringクラスの操作の側面に精通している必要があり、必要に応じてStringBuilderに切り替える必要があると考えています。

    ちなみに、Eric Lippertは、文字列の連結に関連するC#コンパイラの最適化に取り組んだ人物です。

    結論

    おそらく、一見すると、より大きなコードフラグメントの可視性に関連するコンパイラの最適化能力について考えるまで、Stringクラスが演算子「+」を定義しないのは奇妙に思えるかもしれません。たとえば、「+」演算子がStringクラスで定義されている場合、式s =a + b + c + dは、2つの中間文字列、つまりstring.Concat(a、b、 c、d)メソッドを使用すると、連結をより効果的に実行できます。


    1. SQL ServerでCHECK制約を有効にする方法(T-SQLの例)

    2. 1つのSQLクエリに複数の行を挿入する方法–今週のインタビュー質問#069

    3. OracleDatabaseのPL/SQLコレクション方式の概要

    4. SQLServerで削除されたレコードを復元するノウハウ