読者です 読者をやめる 読者になる 読者になる

亀岡的プログラマ日記

京都のベッドタウン、亀岡よりだらだらとお送りいたします。

StringとStringBuilderと、in C#

C#

@irofさんから「.NETでやれ」といわれたのだけれど。

本気で、「まとめる」だけにしますよ。逆アセンブルめんどいし。

+演算子 を使用するべき状況 in C#

というわけで定数は.NETでどうなのよ、といいますとですね。

ソース コードの読みやすさを改善するために、長いリテラル文字列を短い文字列に分割する方法を次の例に示します。 これらの部分は、コンパイル時に単一の文字列に連結されます。 含まれる文字列の数は、実行時のパフォーマンスには影響しません。

方法: 複数の文字列を連結する (C# プログラミング ガイド)

というわけでまず、定数ではコンパイラが面倒見てくれます。

つづいて、定数を変数として使った場合。

        public string GetStr()
        {
            const string s1 = "hoge";
            string s2 = s1 + "fuga";

            return s2;
        }

もちゃんとILSpyすると*1s2は"hogefuga"に展開されています。まぁJavaと同じ。

ちなみに元記事にあった「直感に反する例」をこちらでもやってみると・・・

class A { internal const  String a = "a"; }
class B { public String b = A.a + "b"; }

ちゃんとclass Bでは定数展開されますが、まぁそんだけでClassAが消え去ることはねーです。ここらへんはまぁ、C#はclassファイル的なものはユーザの目に触れませんし。そういうものかと。

んで、別の文になっている場合。

public string Hoge()
{
    String s1 = "abc";
    String s2 = s1 + "def";
    return s2;
}

これは特にコンパイル後もStringBuilderなんぞは使われず、そのまんまです。足し算のまま。

+演算子でもStringBuilderでもたいして変わらない状況 in C#

さてさて、C#的にこういうとき。

// +演算子
foreach (var s in strEnum)
{
    var str = "abc" + s + _suffix;
    SomeMethod(str);
}

// StringBuilder
foreach (String s in strEnum)
{
    var sb = new StringBuilder();
    sb.Append("abc");
    sb.Append(s);
    sb.Append(_suffix);
    SomeMethod(sb.ToString());
}

どうなるかっていうとどうにもなりません。JavaみたいにStringBuilder勝手に使ってくれたりはしません。まぁそこまでせんでもパフォーマンス的に影響でない、という判断なんでしょう。よっぽど気になるなら後者でもいいですけれど、たぶん体制に影響でないです、この程度だと。

追記

パフォーマンスに影響出るどころか、こっちのほうが遅いです。
詳しくはこちらへ → http://posaune.hatenablog.com/entry/2013/03/27/001545

ちなみに僕でもここはString.Formatを使いたい所です。

// +演算子 + Format
foreach (var s in strEnum)
{
    var str = string.Format("abc{0}{1}", s, _suffix);
    SomeMethod(str);
}

// StringBuilder + Format
foreach (var s in strEnum)
{
    var sb = new StringBuilder();
    sb.AppendFormat("abc{0}{1}", s, _suffix);
    SomeMethod(sb.ToString());
}

StringBuilder.AppendFormatはそこそこ便利ですね!

+演算子を使用すべきでない状況 in C#

Javaと同様加算処理しかしないループの中です!以上!

var acq = "";
foreach (var s in strEnum)
{
    acq += s;
}
SomeMethod(acq);

これだとループ分stringが生成されるので、まぁやめたほうが。

つまり、こうです。

var builder = new StringBuilder();
foreach (var s in strEnum)
{
    builder.Append(s);
}
SomeMethod(builder.ToString());
でも結局繰り返しなんてLinqだし・・・

でもやっぱこう書くくらいなら、こっちですよね。

strEnum.Aggregate(new StringBuilder(), (sb, s) => sb.Append(s)).ToString();

結論

  • 少ない連結 → +でもFormatでも見やすい方で。
  • 中で色々処理する場合 → +でもFormatでも見やすい方で。StringBuilderでもいいけれど。
  • 単純連結のみのLoop → StringBuilder使ったほうがいいけれど、とりあえずループはLinqにするよね?

ちなみにString.Concatと加算はほぼ同じ処理らしいです。参考まで。

追記

というわけでほぼじゃなくておんなじです。dobonさんにも書いてありますね。

文字列を連結するメソッドとして、String.Concatメソッドがあります。String.Concatメソッドを使ったときと連結演算子を使ったときの違いは、全くありません。ビルドするとどちらもString.Concatが使われるようになります。

文字列を連結する: .NET Tips: C#, VB.NET

*1:結局逆アセンブルしちゃったし・・・