Использует ли конкатенация строк StringBuilder внутри? - PullRequest
12 голосов
/ 20 мая 2010

Трое из моих коллег только что сказали мне, что нет причин использовать StringBuilder вместо конкатенации с использованием оператора +.Другими словами, это нормально делать с кучей строк: myString1 + myString2 + myString3 + myString4 + mySt...

Обоснование, которое они использовали, было то, что начиная с .NET 2, компилятор C # будет строить тот же IL, если вы используете + оператор, как если бы вы использовали StringBuilder.

Это для меня новость.Они верны?

Ответы [ 8 ]

25 голосов
/ 20 мая 2010

Нет, они не верны. Конкатенация строк создает новый string, тогда как StringBuilder использует буфер переменного размера для построения строки, создавая объект string только при вызове ToString().

В Интернете много дискуссий о методах конкатенации строк, если вы хотите прочитать эту тему дальше. Большинство фокусируется на эффективности различных методов при использовании в циклах. В этом сценарии StringBuilder быстрее по сравнению с конкатенацией строк с использованием строковых операторов для конкатенаций из 10 или более строк , которые должны указывать, что он должен использовать метод, отличный от конкатенации.

Тем не менее, если вы объединяете постоянные строковые значения, строковые операторы будут лучше, потому что компилятор откажется от них, и если вы выполняете нецикличную конкатенацию, использование операторов будет лучше, так как они должны привести к одиночный звонок на string.Concat.

16 голосов
/ 20 мая 2010

Нет, они не верны, это не даст тот же IL:

static string StringBuilder()
{
    var s1 = "s1";
    var s2 = "s2";
    var s3 = "s3";
    var s4 = "s4";
    var sb = new StringBuilder();
    sb.Append(s1).Append(s2).Append(s3).Append(s4);
    return sb.ToString();
}

static string Concat()
{
    var s1 = "s1";
    var s2 = "s2";
    var s3 = "s3";
    var s4 = "s4";
    return s1 + s2 + s3 + s4;
}

IL StringBuilder:

.method private hidebysig static string StringBuilder() cil managed
{
    .maxstack 2
    .locals init (
        [0] string s1,
        [1] string s2,
        [2] string s3,
        [3] string s4,
        [4] class [mscorlib]System.Text.StringBuilder sb)
    L_0000: ldstr "s1"
    L_0005: stloc.0 
    L_0006: ldstr "s2"
    L_000b: stloc.1 
    L_000c: ldstr "s3"
    L_0011: stloc.2 
    L_0012: ldstr "s4"
    L_0017: stloc.3 
    L_0018: newobj instance void [mscorlib]System.Text.StringBuilder::.ctor()
    L_001d: stloc.s sb
    L_001f: ldloc.s sb
    L_0021: ldloc.0 
    L_0022: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0027: ldloc.1 
    L_0028: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_002d: ldloc.2 
    L_002e: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0033: ldloc.3 
    L_0034: callvirt instance class [mscorlib]System.Text.StringBuilder [mscorlib]System.Text.StringBuilder::Append(string)
    L_0039: pop 
    L_003a: ldloc.s sb
    L_003c: callvirt instance string [mscorlib]System.Object::ToString()
    L_0041: ret 
}

IL Concat:

.method private hidebysig static string Concat() cil managed
{
    .maxstack 4
    .locals init (
        [0] string s1,
        [1] string s2,
        [2] string s3,
        [3] string s4)
    L_0000: ldstr "s1"
    L_0005: stloc.0 
    L_0006: ldstr "s2"
    L_000b: stloc.1 
    L_000c: ldstr "s3"
    L_0011: stloc.2 
    L_0012: ldstr "s4"
    L_0017: stloc.3 
    L_0018: ldloc.0 
    L_0019: ldloc.1 
    L_001a: ldloc.2 
    L_001b: ldloc.3 
    L_001c: call string [mscorlib]System.String::Concat(string, string, string, string)
    L_0021: ret 
}

Также вы можете найти эту статью интересной.

5 голосов
/ 20 мая 2010

Нет, это не так. Они определенно генерируют разные IL. Используются разные вызовы: String.Concat в случае не StringBuilder.

String.Concat вызывает закрытый метод с именем ConcatArray, который выделяет новую строку достаточно долго, чтобы сохранить конечный результат. Таким образом, это совсем другое, но это не значит, что объединение с использованием оператора + менее эффективно, чем использование StringBuilder. На самом деле, это почти наверняка более эффективно. Кроме того, в случае конкатенации констант это делается во время компиляции.

Однако, когда вы делаете конкатенацию в цикле, компилятор не может выполнить такую ​​оптимизацию. В таких случаях использование StringBuilder было бы лучше для разумно длинных строк.

4 голосов
/ 20 мая 2010

Ответ в том, что это зависит от того, как вы соединяете. Если вы используете оператор + со статическими строками, то ваши друзья правы - вам не нужен строитель строк. Однако если вы используете строковые переменные или оператор + =, то вы перераспределяете строки.

Чтобы действительно выяснить, что здесь происходит, нужно написать некоторый код и затем декомпилировать его.

Давайте создадим некоторый тестовый код и рассмотрим его в Reflector, используя представление IL (или вы можете использовать ILDASM, в зависимости от того, что вы предпочитаете

Итак, во-первых, базовый уровень - этот метод не объединяется вообще:


static void NoConcat()
{
  string test = "Hello World";
}

Теперь вот ИЛ:


.method private hidebysig static void NoConcat() cil managed
{
    .maxstack 1
    .locals init (
        [0] string test)
    L_0000: nop 
    L_0001: ldstr "Hello World"  <----------NO reallocation!
    L_0006: stloc.0 
    L_0007: ret 
}

Хорошо, никаких сюрпризов, верно?

Теперь давайте рассмотрим некоторый код, который определенно перераспределяет строку, поэтому мы знаем, как это выглядит:


static void Concat2()
{
  string test = "Hello";
  test += " ";
  test += "World";
}

Вот IL, обратите внимание на перераспределения (он вызывает string.Concat, который вызывает выделение новой строки):


.method private hidebysig static void Concat2() cil managed
{
    .maxstack 2
    .locals init (
        [0] string test)
    L_0000: nop 
    L_0001: ldstr "Hello"
    L_0006: stloc.0 
    L_0007: ldloc.0 
    L_0008: ldstr " "
    L_000d: call string [mscorlib]System.String::Concat(string, string)
    L_0012: stloc.0 
    L_0013: ldloc.0 
    L_0014: ldstr "World"
    L_0019: call string [mscorlib]System.String::Concat(string, string)
    L_001e: stloc.0 
    L_001f: ret 
}

Хорошо, теперь как насчет конкатенации, которая не вызывает перераспределения - мы собираемся объединить статические строки с помощью оператора "+":


static void Concat1()
{
  string test = "Hello" + " " + "World";
}

Вот ИЛ - посмотрите, какой умный компилятор! Он не использует concat - он идентичен первому примеру:


.method private hidebysig static void Concat1() cil managed
{
    .maxstack 1
    .locals init (
        [0] string test)
    L_0000: nop 
    L_0001: ldstr "Hello World"
    L_0006: stloc.0 
    L_0007: ret 
}

Теперь давайте немного повеселимся. Что если мы смешаем статические строки и переменные? (это то место, где вам может быть лучше использовать string Builder)


static void Concat3(string text)
{
  string test = "Hello" + " " + text + " World";
}

И Ил. Обратите внимание, что он был достаточно умен, чтобы объединить «Hello» и «» в качестве константы, но он все равно должен выполнить конкат для текстовой переменной:


.method private hidebysig static void Concat3(string text) cil managed
{
    .maxstack 3
    .locals init (
        [0] string test)
    L_0000: nop 
    L_0001: ldstr "Hello "
    L_0006: ldarg.0 
    L_0007: ldstr " World"
    L_000c: call string [mscorlib]System.String::Concat(string, string, string)
    L_0011: stloc.0 
    L_0012: ret 
}

1 голос
/ 20 мая 2010

Я обычно следую следующим правилам:

  1. Если число дочерних строк известно, используйте конкатенацию. Это касается ситуации, подобной str1 + str2 + str3 + ..., независимо от того, сколько их.

  2. Если дочерние строки уже есть в массиве, используйте string.join

  3. Если строится строка в цикле, используйте StringBuilder

0 голосов
/ 02 июля 2010

Нет, конкатенация строк не использует StringBuilder внутри. Однако в вашем конкретном примере использование StringBuilder не дает никаких преимуществ.

Это хорошо для нескольких строк (вы создаете только одну новую строку):

myString = myString + myString2 + myString3 + myString4 + mySt...

Это не так (вы создаете и выделяете 4 строки и т. Д.):

myString = myString + myString2;
myString = myString + myString3;
myString = myString + myString4;
myString = myString + myString5;

Из всех вопросов, связанных со стековым потоком, по этому вопросу, это один из лучших ответов: String vs. StringBuilder

Ищите два ответа: один от Джея Базузи и один от Джеймса Керрана.

Кроме того, НАСТОЯТЕЛЬНО РЕКОМЕНДУЕТСЯ, Джефф Этвуд использует реальное тестирование для сравнения этих и других сценариев объединения / построения строк, здесь: http://www.codinghorror.com/blog/2009/01/the-sad-tragedy-of-micro-optimization-theater.html

0 голосов
/ 20 мая 2010

Существует огромная разница в производительности между конкатенацией строк и StringBuidler. У нас был веб-сервис, который был слишком медленным. Мы изменили все строковые кошки на StringBuilder.Appends, и это стало намного быстрее!

0 голосов
/ 20 мая 2010

Небольшая разница между String и StringBuilder:

Конкатенация строки создаст новый строковый объект, являющийся результатом конкатенации. Объединение StringBuilder изменяет строковый объект.

Так что они не верны.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...