La mejor manera de eliminar el último carácter de una cadena creada con stringbuilder

90

Tengo lo siguiente

data.AppendFormat("{0},",dataToAppend);

El problema con esto es que lo estoy usando en un bucle y habrá una coma de prueba. ¿Cuál es la mejor forma de eliminar la coma al final?

¿Tengo que cambiar los datos a una cadena de la subcadena?

Wesley Skeen
fuente
10
string.Join(",", yourCollection)? Editar: agregado como respuesta.
Vlad
1
¿Intentaste stackoverflow.com/questions/5701163/… ?
andreister
@Chris: de esta manera no necesitas un StringBuilder en absoluto.
Vlad
tal vez pueda evitar agregar la coma en lugar de eliminarla después. Ver: stackoverflow.com/questions/581448/… (Respuesta de Jon Skeet)
Paolo Falabella
@Vlad Sí lo siento, leí mal eso; Pensé que lo estabas ofreciendo como una sugerencia para alterar la cuerda construida final, no como un reemplazo para su bucle por completo. (Pensé que borré mi comentario a tiempo, ¡supongo que no!)
Chris Sinclair

Respuestas:

222

La forma más sencilla y eficaz es ejecutar este comando:

data.Length--;

al hacer esto, mueve el puntero (es decir, el último índice) hacia atrás un carácter, pero no cambia la mutabilidad del objeto. De hecho, StringBuilderes mejor borrar a Lengthtambién (pero en realidad use el Clear()método para mayor claridad porque así es como se ve su implementación):

data.Length = 0;

de nuevo, porque no cambia la tabla de asignación. Piense en ello como si dijera que ya no quiero reconocer estos bytes. Ahora, incluso al llamar ToString(), no reconocerá nada más allá de su Length, bueno, no puede. Es un objeto mutable que asigna más espacio del que le proporcionas, simplemente está construido de esta manera.

Mike Perrenoud
fuente
2
re data.Length = 0;: eso es exactamente lo que StringBuilder.Clearhace, por lo que es preferible usarlo StringBuilder.Clearpara tener claridad de intención.
Eren Ersönmez
@ ErenErsönmez, bastante amigo, debería haber dicho más claramente que eso es lo que Clear()hace, pero es gracioso. Esa es la primera línea del Clear()método. Pero, ¿sabía que la interfaz en realidad emite un return this;. Eso es lo que me mata. Configurando los Length = 0cambios la referencia que ya tienes, ¿por qué regresar tú mismo?
Mike Perrenoud
12
Creo que es para poder usarlo de manera "fluida". Appendregresa a sí mismo también.
Eren Ersönmez
43

Solo usa

string.Join(",", yourCollection)

De esta manera no necesitas el StringBuildery el bucle.




Adición larga sobre el caso asincrónico. A partir de 2019, no es una configuración rara cuando los datos llegan de forma asincrónica.

En caso de que sus datos estén en una recopilación asíncrona, no hay string.Joinsobrecarga IAsyncEnumerable<T>. Pero es fácil crear uno manualmente, pirateando el código destring.Join :

public static class StringEx
{
    public static async Task<string> JoinAsync<T>(string separator, IAsyncEnumerable<T> seq)
    {
        if (seq == null)
            throw new ArgumentNullException(nameof(seq));

        await using (var en = seq.GetAsyncEnumerator())
        {
            if (!await en.MoveNextAsync())
                return string.Empty;

            string firstString = en.Current?.ToString();

            if (!await en.MoveNextAsync())
                return firstString ?? string.Empty;

            // Null separator and values are handled by the StringBuilder
            var sb = new StringBuilder(256);
            sb.Append(firstString);

            do
            {
                var currentValue = en.Current;
                sb.Append(separator);
                if (currentValue != null)
                    sb.Append(currentValue);
            }
            while (await en.MoveNextAsync());
            return sb.ToString();
        }
    }
}

Si los datos llegan de forma asincrónica pero la interfaz IAsyncEnumerable<T>no es compatible (como se menciona en los comentarios SqlDataReader), es relativamente fácil envolver los datos en un IAsyncEnumerable<T>:

async IAsyncEnumerable<(object first, object second, object product)> ExtractData(
        SqlDataReader reader)
{
    while (await reader.ReadAsync())
        yield return (reader[0], reader[1], reader[2]);
}

y usarlo:

Task<string> Stringify(SqlDataReader reader) =>
    StringEx.JoinAsync(
        ", ",
        ExtractData(reader).Select(x => $"{x.first} * {x.second} = {x.product}"));

Para poder usarlo Select, necesitará usar nuget package System.Interactive.Async. Aquí puede encontrar un ejemplo compilable.

Vlad
fuente
12
La mejor respuesta no es la que aborda el problema, sino la que lo previene.
Último Tribunal
1
@Seabizkit: ¡Por supuesto! Toda la pregunta es sobre C #, por cierto.
Vlad
1
@Vlad lo entiendo, solo verifiqué dos veces ya que estoy haciendo una prueba simple como una prueba sin procesar y no produjeron lo mismo. string.Join(",", yourCollection)todavía tiene un ,al final. por lo que lo anterior, string.Join(",", yourCollection)es decir, es ineficiente y no lo elimina por sí solo.
Seabizkit
2
@Seabizkit: ¡Muy extraño! ¿Podrías publicar un ejemplo? En mi código funciona perfectamente: ideone.com/aj8PWR
Vlad
2
Años de usar un bucle para construir un generador de cadenas y luego eliminar la coma final y podría haber usado esto. ¡Gracias por el consejo!
Caverman
11

Utilice lo siguiente después del ciclo.

.TrimEnd(',')

o simplemente cambia a

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Sam Leach
fuente
4
Está usando StringBuilder, no string. Además, es bastante ineficaz: primero conversión a cuerda y luego recorte.
Piotr Stapp
ostring.Join(",", input)
Tvde1
10

Qué tal esto..

string str = "The quick brown fox jumps over the lazy dog,";
StringBuilder sb = new StringBuilder(str);
sb.Remove(str.Length - 1, 1);
Pankaj
fuente
7

Prefiero manipular la longitud del constructor de cadenas:

data.Length = data.Length - 1;
bastos.sergio
fuente
4
¿Por qué no simplemente data.Length--o --data.Length?
Gone Coding
Normalmente uso data.Length, pero en un caso tuve que retroceder 2 caracteres debido a un valor en blanco después del carácter que quería eliminar. El recorte tampoco funcionó en ese caso, así que data.Length = data.Length - 2; trabajó.
Caverman
Trim devuelve una nueva instancia de una cadena, no altera el contenido del objeto
stringbuilder
@GoneCoding Visual Basic .NET no es compatible --o ++ puede usarlo data.Length -= 1, o esta respuesta también funcionará.
Jason S
3

Te recomiendo que cambies tu algoritmo de bucle:

  • Agregue la coma no DESPUÉS del elemento, sino ANTES
  • Use una variable booleana, que comience con falso, suprima la primera coma
  • Establezca esta variable booleana en verdadera después de probarla
Eugen Rieck
fuente
2
Esta es probablemente la menos eficiente de todas las sugerencias (y requiere más código).
Gone Coding
1
Mira la respuesta de @Vlad
Noctis
3

Debe usar el string.Joinmétodo para convertir una colección de elementos en una cadena delimitada por comas. Se asegurará de que no haya comas al principio o al final, así como también garantizará que la cadena se construya de manera eficiente (sin cadenas intermedias innecesarias).

Servy
fuente
2

Sí, conviértalo en una cadena una vez que finalice el ciclo:

String str = data.ToString().TrimEnd(',');
DonBoitnott
fuente
3
es bastante ineficaz: primero conversión a cuerda y luego recorte.
Piotr Stapp
2
@Garath Si quisieras decir "ineficaz", no estaría en desacuerdo. Pero sería efectivo.
DonBoitnott
2

Tienes dos opciones. El primero es un Removemétodo de uso muy fácil , es bastante efectivo. La segunda forma es usar ToStringcon índice inicial y índice final ( documentación de MSDN )

Piotr Stapp
fuente
1

La forma más sencilla sería utilizar el método Join ():

public static void Trail()
{
    var list = new List<string> { "lala", "lulu", "lele" };
    var data = string.Join(",", list);
}

Si realmente necesita StringBuilder, recorte la coma final después del bucle:

data.ToString().TrimEnd(',');
estudiante
fuente
4
data.ToString().TrimEnd(',');es ineficiente
bastos.sergio
1
Además, es posible que no desee convertir el objeto StringBuilder en String, ya que puede tener varias líneas que terminan con ","
Fandango68