2010-08-25 9 views

Respuesta

51

En .NET 2.0 que utiliza la clase String internamente. String solo es inmutable fuera del espacio de nombre System, por lo que StringBuilder puede hacerlo.

En .NET 4.0 String se ha cambiado para utilizar char[].

En 2.0 StringBuilder veía así

public sealed class StringBuilder : ISerializable 
{ 
    // Fields 
    private const string CapacityField = "Capacity"; 
    internal const int DefaultCapacity = 0x10; 
    internal IntPtr m_currentThread; 
    internal int m_MaxCapacity; 
    internal volatile string m_StringValue; // HERE ---------------------- 
    private const string MaxCapacityField = "m_MaxCapacity"; 
    private const string StringValueField = "m_StringValue"; 
    private const string ThreadIDField = "m_currentThread"; 

Pero en 4.0 se ve así:

public sealed class StringBuilder : ISerializable 
{ 
    // Fields 
    private const string CapacityField = "Capacity"; 
    internal const int DefaultCapacity = 0x10; 
    internal char[] m_ChunkChars; // HERE -------------------------------- 
    internal int m_ChunkLength; 
    internal int m_ChunkOffset; 
    internal StringBuilder m_ChunkPrevious; 
    internal int m_MaxCapacity; 
    private const string MaxCapacityField = "m_MaxCapacity"; 
    internal const int MaxChunkSize = 0x1f40; 
    private const string StringValueField = "m_StringValue"; 
    private const string ThreadIDField = "m_currentThread"; 

Así que, evidentemente, se cambió el uso de un string al uso de una char[].

EDIT: respuesta actualizada para reflejar los cambios en .NET 4 (que acabo de descubrir).

+0

No tenía idea ... Creo que voy a hacer algo de magia reflector para satisfacer mi curiosidad :) – cwap

+0

@Brian: hasta donde sé, contiene una matriz 'Char' internamente, no una' Cadena' (al menos en .NET 4, quizás esto ha cambiado?) –

+0

@Fredrik - en la implementación de MS, realmente es una 'cadena' que se muta –

7

En realidad, no utiliza el búfer de caracteres interno. Solo cuando la capacidad del buffer se agote, asignará un nuevo buffer. La operación de agregar simplemente agregará a este búfer, el objeto de cadena se creará cuando se le llame al método ToString(); de ahí en adelante, es recomendable para muchas concatenaciones de cadenas ya que cada cadena tradicional de concaturas crearía una nueva cadena. También puede especificar la capacidad inicial del generador de cadenas si tiene una idea aproximada para evitar asignaciones múltiples.

Editar: La gente está señalando que mi entendimiento es incorrecto. favor ignorar la respuesta (prefiero no lo elimine, - que se mantendrá como una prueba de mi ignorancia :-)

+1

Actúa * como si * fuera un buffer de caracteres, pero realmente es una instancia de 'cadena 'mutada. Honesto. –

+0

Gracias Marc - Estaba bajo la impresión de que utiliza el buffer de caracteres. Significa que tendría una implementación nativa para mutar el objeto de cadena. – VinayC

+0

seguro, pero es una clase básica de framework. Tiene acceso a la implementación nativa. –

2

Si miro NET Reflector en .NET 2 ya veremos esto:

public StringBuilder Append(string value) 
{ 
    if (value != null) 
    { 
     string stringValue = this.m_StringValue; 
     IntPtr currentThread = Thread.InternalGetCurrentThread(); 
     if (this.m_currentThread != currentThread) 
     { 
      stringValue = string.GetStringForStringBuilder(stringValue, stringValue.Capacity); 
     } 
     int length = stringValue.Length; 
     int requiredLength = length + value.Length; 
     if (this.NeedsAllocation(stringValue, requiredLength)) 
     { 
      string newString = this.GetNewString(stringValue, requiredLength); 
      newString.AppendInPlace(value, length); 
      this.ReplaceString(currentThread, newString); 
     } 
     else 
     { 
      stringValue.AppendInPlace(value, length); 
      this.ReplaceString(currentThread, stringValue); 
     } 
    } 
    return this; 
} 

Por lo que es una instancia de secuencia mutada ...

EDITAR Excepto en .NET 4 es a char[]

+0

@Richard: gracias por la EDITACIÓN. No sabía ese hecho. –

2

Si desea ver una de las implementaciones posibles (que es similar a la que se envía con la implementación de Microsoft hasta v3.5) puede ver the source of the Mono one en github.

2

he hecho una pequeña muestra para demostrar cómo funciona StringBuilder en .NET 4. El contrato es

public interface ISimpleStringBuilder 
{ 
    ISimpleStringBuilder Append(string value); 
    ISimpleStringBuilder Clear(); 
    int Lenght { get; } 
    int Capacity { get; } 
} 

y esta es una aplicación muy básica

public class SimpleStringBuilder : ISimpleStringBuilder 
{ 
    public const int DefaultCapacity = 32; 

    private char[] _internalBuffer; 

    public int Lenght { get; private set; } 
    public int Capacity { get; private set; } 

    public SimpleStringBuilder(int capacity) 
    { 
     Capacity = capacity; 
     _internalBuffer = new char[capacity]; 
     Lenght = 0; 
    } 

    public SimpleStringBuilder() : this(DefaultCapacity) { } 

    public ISimpleStringBuilder Append(string value) 
    { 
     char[] data = value.ToCharArray(); 

     //check if space is available for additional data 
     InternalEnsureCapacity(data.Length); 

     foreach (char t in data) 
     { 
      _internalBuffer[Lenght] = t; 
      Lenght++; 
     } 

     return this; 
    } 

    public ISimpleStringBuilder Clear() 
    { 
     _internalBuffer = new char[Capacity]; 
     Lenght = 0; 
     return this; 
    } 

    public override string ToString() 
    { 
     //use only non-null ('\0') characters 
     var tmp = new char[Lenght]; 
     for (int i = 0; i < Lenght; i++) 
     { 
      tmp[i] = _internalBuffer[i]; 
     } 
     return new string(tmp); 
    } 

    private void InternalExpandBuffer() 
    { 
     //double capacity by default 
     Capacity *= 2; 

     //copy to new array 
     var tmpBuffer = new char[Capacity]; 
     for (int i = 0; i < _internalBuffer.Length; i++) 
     { 
      char c = _internalBuffer[i]; 
      tmpBuffer[i] = c; 
     } 
     _internalBuffer = tmpBuffer; 
    } 

    private void InternalEnsureCapacity(int additionalLenghtRequired) 
    { 
     while (Lenght + additionalLenghtRequired > Capacity) 
     { 
      //not enough space in the current buffer  
      //double capacity 
      InternalExpandBuffer(); 
     } 
    } 
} 

Este código no se thread- seguro, no realiza ninguna validación de entrada y no está utilizando la magia interna (insegura) de System.String. Sin embargo, demuestra la idea detrás de la clase StringBuilder.

Algunas pruebas unitarias y el código de muestra completo se pueden encontrar en github.

22

La respuesta aceptada pierde la marca por una milla.El cambio significativo a StringBuilder en 4.0 no es el cambio de string inseguro a char[] - es el hecho de que StringBuilder es ahora en realidad una lista de StringBuilder instancias conectadas.


La razón de este cambio debería ser obvio: ahora nunca hay una necesidad de reasignar el búfer (una operación costosa, ya que, junto con la asignación de más memoria, también hay que copiar todo el contenido de la el buffer anterior al nuevo).

Esto significa llamar ToString() es ahora un poco más lento, ya que la cadena final tiene que ser calculada, pero haciendo un gran número de operaciones Append() es ahora significativamente más rápido. Esto encaja con el típico caso de uso para StringBuilder: un montón de llamadas a Append(), seguido de una sola llamada a ToString().


Puede encontrar los puntos de referencia here. ¿La conclusión? La nueva lista enlazada StringBuilder usa marginalmente más memoria, pero es significativamente más rápida para el típico caso de uso.

Cuestiones relacionadas