¿Cómo trunco ​​una cadena .NET?


406

Me gustaría truncar una cadena de modo que su longitud no sea más larga que un valor dado. Estoy escribiendo en una tabla de base de datos y quiero asegurarme de que los valores que escribo cumplan con la restricción del tipo de datos de la columna.

Por ejemplo, sería bueno si pudiera escribir lo siguiente:

string NormalizeLength(string value, int maxLength)
{
    return value.Substring(0, maxLength);
}

Desafortunadamente, esto genera una excepción porque maxLengthgeneralmente excede los límites de la cadena value. Por supuesto, podría escribir una función como la siguiente, pero esperaba que algo como esto ya exista.

string NormalizeLength(string value, int maxLength)
{
    return value.Length <= maxLength ? value : value.Substring(0, maxLength);
} 

¿Dónde está la esquiva API que realiza esta tarea? ¿Hay uno?


24
Para el registro, las cadenas son inmutables, no puede truncarlas, solo puede devolver una copia truncada de ellas. Nitpicky, lo sé.
John Weldon

2
@ John Weldon: Probablemente por eso la función miembro no existe, no sigue la semántica del tipo de datos. En una nota al margen, le StringBuilderpermite truncar acortando la longitud, pero aún necesita realizar la verificación de longitud para evitar ensanchar la cadena.
Steve Guidi

1
Independientemente de la solución que elija, asegúrese de agregar una marca para una cadena nula antes de llamar a Substring o acceder a la propiedad Longitud.
Rayo

3
@SteveGuidi - Si ese fuera el caso, entonces no habría funciones como Trim o Reemplazar, que enfrentan problemas semánticos similares
Chris Rogers

1
@JohnWeldon Son más quisquillosos que los propios Microsoft, como sucede: están felices de documentar, por ejemplo, .Trim()de una manera que hace que parezca engañoso que muta la cadena: "Elimina todos los caracteres de espacio en blanco iniciales y finales del objeto de cadena actual ".
Mark Amery el

Respuestas:


620

No hay un Truncate()método en cadena, desafortunadamente. Tienes que escribir este tipo de lógica tú mismo. Sin embargo, lo que puede hacer es envolver esto en un método de extensión para que no tenga que duplicarlo en todas partes:

public static class StringExt
{
    public static string Truncate(this string value, int maxLength)
    {
        if (string.IsNullOrEmpty(value)) return value;
        return value.Length <= maxLength ? value : value.Substring(0, maxLength); 
    }
}

Ahora podemos escribir:

var someString = "...";
someString = someString.Truncate(2);

55
Gran solución, pero recordé que esto solo funciona en NET 3.5 y versiones posteriores. No lo intentes en NET2.0.
Maestro Jedi Spooky

77
Mientras esté en VS 2008, y presumiblemente VS 2010, aún podría hacerlo incluso si apunta a .Net 2.0. danielmoth.com/Blog/…
Marque

44
Esto fallará cuando maxLengthsea ​​un valor negativo.
Bernard

42
@Bernard, se supone que esto falla si maxLength es negativo. Cualquier otro comportamiento sería inesperado.
bojingo

12
Puede llamar a métodos de extensión en valores nulos.
Joel Malone

127

O en lugar del operador ternario, podría usar Math.min

public static class StringExt
{
    public static string Truncate( this string value, int maxLength )
    {
        if (string.IsNullOrEmpty(value)) { return value; }

        return value.Substring(0, Math.Min(value.Length, maxLength));
    }
}

10
¡Inteligente! Y la siguiente expresión está optimizado para devolver una referencia a la cadena original: value.Substring(0, value.Length).
Steve Guidi

44
Desafortunadamente, no está optimizado para casos donde value.Length es menor que MaxLength, lo que puede ser un caso común en algunos datos. También la propiedad Longitud en la cadena debe estar en mayúscula.
jpierson

1
Esto fallará cuando maxLengthsea ​​un valor negativo.
Bernard

77
@Bernard, también lo harán muchas cosas en el marco ... pero si lo compruebo ... o tengo que cambiar por defecto maxLengtha 0o value.Length; o necesito lanzar un ArgumentOutOfRangeException... que tiene más sentido en este caso, y de Substringtodos modos ya lo arrojó .
CaffGeek

2
Un poco más corto:return string.IsNullOrEmpty(value) ? value : value.Substring(0, Math.Min(value.Length, maxLength));
user1127860

43

Pensé que lanzaría mi implementación ya que creo que cubre todos los casos que han sido abordados por los demás y lo hace de una manera concisa que aún es legible.

public static string Truncate(this string value, int maxLength)
{
    if (!string.IsNullOrEmpty(value) && value.Length > maxLength)
    {
        return value.Substring(0, maxLength);
    }

    return value;
}

Esta solución se basa principalmente en la solución de Ray y abre el método para usarlo como un método de extensión mediante el uso de esta palabra clave tal como lo hace LBushkin en su solución.


Esto fallará cuando maxLengthsea ​​un valor negativo.
Bernard

15
@Bernard: recomendaría no pasar un valor negativo para el argumento maxLength, ya que es un valor inesperado. El método de subcadena adopta el mismo enfoque, por lo que no hay razón para mejorar la excepción que arroja.
jpierson

¿No creo que la verificación IsNullOrEmpty sea necesaria? (1) Si el valor es nulo, no debería haber forma de llamar a este método de extensión. (2) Si el valor es la cadena vacía, la verificación value.Length> maxLength fallará.
Jon Schneider

8
@JonSchneider, se requiere IsNullOrEmpty porque este es un método de extensión. Si tiene una variable de tipo cadena a la que se le ha asignado un valor nulo, el compilador no inserta una comprobación nula antes de llamar a este método. Técnicamente, este sigue siendo un método estático de la clase estática. Entonces: stringVar.Truncate (2) Compila como: StringExt.Truncate (stringVar, 2);
Jeff B

40

Porque las pruebas de rendimiento son divertidas: (usando métodos de extensión de linqpad )

var val = string.Concat(Enumerable.Range(0, 50).Select(i => i % 10));

foreach(var limit in new[] { 10, 25, 44, 64 })
    new Perf<string> {
        { "newstring" + limit, n => new string(val.Take(limit).ToArray()) },
        { "concat" + limit, n => string.Concat(val.Take(limit)) },
        { "truncate" + limit, n => val.Substring(0, Math.Min(val.Length, limit)) },
        { "smart-trunc" + limit, n => val.Length <= limit ? val : val.Substring(0, limit) },
        { "stringbuilder" + limit, n => new StringBuilder(val, 0, Math.Min(val.Length, limit), limit).ToString() },
    }.Vs();

El truncatemétodo fue "significativamente" más rápido. #microoptimization

Temprano

  • truncate10 5788 ticks transcurridos (0.5788 ms) [en 10K repeticiones, 5.788E-05 ms por]
  • smart-trunc10 8206 ticks transcurridos (0.8206 ms) [en 10K repeticiones, 8.206E-05 ms por]
  • stringbuilder10 10557 ticks transcurridos (1.0557 ms) [en 10K repeticiones, 0.00010557 ms por]
  • concat10 45495 ticks transcurridos (4.5495 ms) [en 10K repeticiones, 0.00045495 ms por]
  • newstring10 72535 ticks transcurridos (7.2535 ms) [en 10K repeticiones, 0.00072535 ms por]

Tarde

  • truncamiento44 8835 ticks transcurridos (0.8835 ms) [en 10K repeticiones, 8.835E-05 ms por]
  • stringbuilder44 13106 ticks transcurridos (1.3106 ms) [en 10K repeticiones, 0.00013106 ms por]
  • smart-trunc44 14821 tics transcurridos (1.4821 ms) [en 10K repeticiones, 0.00014821 ms por]
  • newstring44 144324 ticks transcurridos (14.4324 ms) [en 10K repeticiones, 0.00144324 ms por]
  • concat44 174610 ticks transcurridos (17.461 ms) [en 10K repeticiones, 0.0017461 ms por]

Demasiado largo

  • smart-trunc64 6944 ticks transcurridos (0.6944 ms) [en 10K repeticiones, 6.944E-05 ms por]
  • truncate64 7686 ticks transcurridos (0.7686 ms) [en 10K repeticiones, 7.686E-05 ms por]
  • stringbuilder64 13314 ticks transcurridos (1.3314 ms) [en 10K repeticiones, 0.00013314 ms por]
  • newstring64 177481 ticks transcurridos (17.7481 ms) [en 10K repeticiones, 0.00177481 ms por]
  • concat64 241601 tics transcurridos (24.1601 ms) [en 10K repeticiones, 0.00241601 ms por]

¡Gracias por todos los puntos de referencia útiles! ... y Linkpad rocas!
Sunsetquest

nunca me importó que linqpad pudiera hacer esas cosas
jefissu

38

En .NET 4.0 puede usar el Takemétodo:

string.Concat(myString.Take(maxLength));

No probado para la eficiencia!


27

Podrías usar LINQ ... elimina la necesidad de verificar la longitud de la cadena. Es cierto que quizás no sea el más eficiente, pero es divertido.

string result = string.Join("", value.Take(maxLength)); // .NET 4 Join

o

string result = new string(value.Take(maxLength).ToArray());

2
¿Por qué no es esta la respuesta aceptada? Lo más sencillo es escribir su propio método de Extensión que necesita mantener / documentar, o usar algo INTEGRADO como .Take
Don Cheadle

99
@mmcrae Linq podría ser más directo, pero también es mucho más lento. Mi punto de referencia dice ~ 400ms para Linq y solo ~ 24ms para Substring para 1 millón de iteraciones.
Hein Andre Grønnestad

Esta solución no debería usarse nunca. Como se dijo en dos comentarios anteriores, siempre hay asignación de memoria, incluso cuando la cadena existente no es mayor que la longitud máxima. También es muy lento.
Kamarey

15

Hice el mío en una línea como esta

value = value.Length > 1000 ? value.Substring(0, 1000) : value;

2
-1; esto no agrega nada que no esté en la respuesta aceptada.
Mark Amery el

2
@markamery es una alternativa más corta con menos código para escribir y actualizar cuando necesite usarlo. No te gusta No lo use
SeanMC

Rápido, simple y rápido. Esto es lo que necesitaba. ¡Gracias!
Peter

14

Parece que nadie ha publicado esto todavía:

public static class StringExt
{
    public static string Truncate(this string s, int maxLength)
    {
        return s != null && s.Length > maxLength ? s.Substring(0, maxLength) : s;
    }
}

El uso del operador && lo hace marginalmente mejor que la respuesta aceptada.


13

.NET Framework tiene una API para truncar una cadena como esta:

Microsoft.VisualBasic.Strings.Left(string, int);

Pero en una aplicación C #, probablemente preferirás usar la tuya propia que depender de Microsoft.VisualBasic.dll, cuya razón de ser principal es la compatibilidad con versiones anteriores.


"El .NET Framework tiene una API" te estás contradiciendo a ti mismo. Esa es una API de VB.NET
Camilo Terevinto, el

99
@CamiloTerevinto: es una API que se incluye con .NET Framework y se puede llamar desde cualquier lenguaje administrado.
Joe

1
La DLL de VB tiene muchas cosas buenas. ¿Por qué hay tantos desarrolladores de C # en contra?
Michael Z.

Actualmente no hay soporte para .NET Core, desafortunadamente. De hecho, todos los Microsoft.VisualBasic.Stringsmódulos en .NET Core están bastante vacíos .
Mark Amery

1
Si bien estoy de acuerdo con el comentario de Joe, todavía no me siento bien llamando a algo específico para VB desde otros idiomas. Si hay tantas cosas buenas en "VB DLL", ¿por qué no ponerlo en un lugar compartido? ¿Quién sabe qué hará Microsoft con estas cosas mañana? El apoyo de parada o algo ..
Kamarey


6

Sé que esta es una vieja pregunta, pero aquí hay una buena solución:

public static string Truncate(this string text, int maxLength, string suffix = "...")
{
    string str = text;
    if (maxLength > 0)
    {
        int length = maxLength - suffix.Length;
        if (length <= 0)
        {
            return str;
        }
        if ((text != null) && (text.Length > maxLength))
        {
            return (text.Substring(0, length).TrimEnd(new char[0]) + suffix);
        }
    }
    return str;
}

var myString = "hello world"
var myTruncatedString = myString.Truncate(4);

Devoluciones: hola ...


@SarjanWebDev Ese carácter especial aparece como "." en cmd.exe
Neal Ehardt

5

Una variante similar con el operador de propagación nula de C # 6

public static string Truncate(this string value, int maxLength)
{
    return value?.Length <= maxLength ? value : value?.Substring(0, maxLength);
}

Tenga en cuenta que esencialmente estamos verificando si valuees nulo dos veces aquí.


5

Todavía no hay método Truncar en 2016 para cadenas de C #. Pero - Usando C # 6.0 Sintaxis:

public static class StringExtension
{
  public static string Truncate(this string s, int max) 
  { 
    return s?.Length > max ? s.Substring(0, max) : s ?? throw new ArgumentNullException(s); 
  }
}

Funciona a las mil maravillas:

"Truncate me".Truncate(8);
Result: "Truncate"

4

Tomando @CaffGeek y simplificándolo:

public static string Truncate(this string value, int maxLength)
    {
        return string.IsNullOrEmpty(value) ? value : value.Substring(0, Math.Min(value.Length, maxLength));
    }

4

Tenga en cuenta que truncar una cadena no solo significa simplemente cortar una cadena en una longitud específica solo, sino que debe tener cuidado de no dividir la palabra.

por ejemplo, cadena: esta es una cadena de prueba.

Quiero cortarlo a las 11. Si utilizamos alguno de los métodos dados anteriormente, el resultado será

esto es un te

Esto no es lo que queremos

El método que estoy usando también puede no ser tan perfecto, pero puede manejar la mayoría de las situaciones.

public string CutString(string source, int length)
{
        if (source== null || source.Length < length)
        {
            return source;
        }
        int nextSpace = source.LastIndexOf(" ", length);
        return string.Format("{0}...", input.Substring(0, (nextSpace > 0) ? nextSpace : length).Trim());
} 

4

Por qué no:

string NormalizeLength(string value, int maxLength)
{
    //check String.IsNullOrEmpty(value) and act on it. 
    return value.PadRight(maxLength).Substring(0, maxLength);
}

es decir, en el evento value.Length < maxLength, los espacios del pad hasta el final o truncar el exceso.


Genera el doble de objetos de cadena y podría arrojar una NullReferenceException de la llamada PadRight si el valor es nulo, lo cual es inapropiado, debería ser una ArgumentNullException.
Jeremy

1
@Jeremy No entiendo "podría arrojar una NullReferenceException de la llamada PadRight si el valor es nulo"; ¿No he mencionado "// comprobar string.IsNullOrEmpty (valor) y actuar en consecuencia"?
Sri

3

Por si acaso no hay suficientes respuestas aquí, aquí está la mía :)

public static string Truncate(this string str, 
                              int totalLength, 
                              string truncationIndicator = "")
{
    if (string.IsNullOrEmpty(str) || str.Length < totalLength) 
        return str;

    return str.Substring(0, totalLength - truncationIndicator.Length) 
           + truncationIndicator;
}

usar:

"I use it like this".Truncate(5,"~")

2

En aras de la (sobre) complejidad, agregaré mi versión sobrecargada que reemplaza los últimos 3 caracteres con puntos suspensivos con respecto al parámetro maxLength.

public static string Truncate(this string value, int maxLength, bool replaceTruncatedCharWithEllipsis = false)
{
    if (replaceTruncatedCharWithEllipsis && maxLength <= 3)
        throw new ArgumentOutOfRangeException("maxLength",
            "maxLength should be greater than three when replacing with an ellipsis.");

    if (String.IsNullOrWhiteSpace(value)) 
        return String.Empty;

    if (replaceTruncatedCharWithEllipsis &&
        value.Length > maxLength)
    {
        return value.Substring(0, maxLength - 3) + "...";
    }

    return value.Substring(0, Math.Min(value.Length, maxLength)); 
}

2

Mis dos centavos con una longitud de ejemplo de 30:

  var truncatedInput = string.IsNullOrEmpty(input) ? 
      string.Empty : 
      input.Substring(0, Math.Min(input.Length, 30));

1

Prefiero la respuesta de jpierson, pero ninguno de los ejemplos que puedo ver aquí está manejando un parámetro maxLength no válido, como cuando maxLength <0.

Las opciones serían manejar el error en un intento / captura, sujetar el parámetro maxLength min a 0 o si maxLength es menor que 0 devolver una cadena vacía.

Código no optimizado:

public string Truncate(this string value, int maximumLength)
{
    if (string.IsNullOrEmpty(value) == true) { return value; }
    if (maximumLen < 0) { return String.Empty; }
    if (value.Length > maximumLength) { return value.Substring(0, maximumLength); }
    return value;
}

3
Tenga en cuenta que en mi implementación elegí no manejar el caso en el que maximumLength es menor que 0 porque pensé que lo único que haría sería lanzar un ArgumentOutOfRangeExcpetion que esencialmente es lo que string.Substring () hace por mí.
jpierson

1

Aquí hay una solución de vb.net, marque que la instrucción if (aunque fea) mejora el rendimiento porque no necesitamos la instrucción de subcadena cuando la cadena ya es más pequeña que la longitud máxima ... Al hacerla una extensión de cadena, es fácil de usar. ..

 <System.Runtime.CompilerServices.Extension()> _
    Public Function Truncate(String__1 As String, maxlength As Integer) As String
        If Not String.IsNullOrEmpty(String__1) AndAlso String__1.Length > maxlength Then
            Return String__1.Substring(0, maxlength)
        Else
            Return String__1
        End If
    End Function

En VB.net puede reemplazar "Not String.IsNullOrEmpty (String__1)" con "String__1 <> Nothing". Es un poco más corto. El valor predeterminado para las cadenas es una cadena vacía. El uso de "<> Nothing" verifica los casos de cadena nula y vacía Pruébelo con: Truncate ("", 50) y Truncate (Nothing, 50)
jrjensen

En VB puedes hacer Left (string, maxlength)
Michael Z.

1

Sé que ya hay un montón de respuestas, pero mi necesidad era mantener intactos el principio y el final de la cadena, pero acortarla por debajo de la longitud máxima.

    public static string TruncateMiddle(string source)
    {
        if (String.IsNullOrWhiteSpace(source) || source.Length < 260) 
            return source;

        return string.Format("{0}...{1}", 
            source.Substring(0, 235),
            source.Substring(source.Length - 20));
    }

Esto es para crear URL de SharePoint que tengan una longitud máxima de 260 caracteres.

No hice de la longitud un parámetro ya que es una constante 260. Tampoco hice de la primera longitud de subcadena un parámetro porque quiero que se rompa en un punto específico. Finalmente, la segunda subcadena es la longitud de la fuente: 20, ya que conozco la estructura de la carpeta.

Esto podría adaptarse fácilmente a sus necesidades específicas.


1

Sé que ya hay un montón de respuestas aquí, pero esta es con la que he ido, que maneja tanto las cadenas nulas como la situación en la que la longitud pasada es negativa:

public static string Truncate(this string s, int length)
{
    return string.IsNullOrEmpty(s) || s.Length <= length ? s 
        : length <= 0 ? string.Empty 
        : s.Substring(0, length);
}

1

En C # 8, se puede usar la nueva función Rangos ...

value = value[..Math.Min(30, value.Length)];

0

No hay nada en .net para esto que yo sepa, aquí está mi versión que agrega "...":

public static string truncateString(string originalString, int length) {
  if (string.IsNullOrEmpty(originalString)) {
   return originalString;
  }
  if (originalString.Length > length) {
   return originalString.Substring(0, length) + "...";
  }
  else {
   return originalString;
  }
}

2
Su versión dará cadenas que son 3 caracteres más largas que la longitud solicitada, en caso de que estén truncadas. Además, los puntos triples son realmente significativos en representación, no lo almacenaría en una base de datos como esa, que es el caso de uso que dio el OP.
MarioDS

0

TruncateString

public static string _TruncateString(string input, int charaterlimit)
{
    int characterLimit = charaterlimit;
    string output = input;

    // Check if the string is longer than the allowed amount
    // otherwise do nothing
    if (output.Length > characterLimit && characterLimit > 0)
    {
        // cut the string down to the maximum number of characters
        output = output.Substring(0, characterLimit);
        // Check if the character right after the truncate point was a space
        // if not, we are in the middle of a word and need to remove the rest of it
        if (input.Substring(output.Length, 1) != " ")
        {
            int LastSpace = output.LastIndexOf(" ");

            // if we found a space then, cut back to that space
            if (LastSpace != -1)
            {
                output = output.Substring(0, LastSpace);
            }
        }
        // Finally, add the "..."
        output += "...";
    }
    return output;
}

2
¿Por qué antecede el nombre de su método público con un guión bajo?
Michael Z.

0

Como una adición a las posibilidades discutidas anteriormente, me gustaría compartir mi solución. Es un método de extensión que permite nulo (devuelve string.Empty) también hay un segundo .Truncate () para usarlo con puntos suspensivos. Cuidado, no es un rendimiento optimizado.

public static string Truncate(this string value, int maxLength) =>
    (value ?? string.Empty).Substring(0, (value?.Length ?? 0) <= (maxLength < 0 ? 0 : maxLength) ? (value?.Length ?? 0) : (maxLength < 0 ? 0 : maxLength));
public static string Truncate(this string value, int maxLength, string ellipsis) =>
    string.Concat(value.Truncate(maxLength - (((value?.Length ?? 0) > maxLength ? ellipsis : null)?.Length ?? 0)), ((value?.Length ?? 0) > maxLength ? ellipsis : null)).Truncate(maxLength);

-1
public static string Truncate( this string value, int maxLength )
    {
        if (string.IsNullOrEmpty(value)) { return value; }

        return new string(value.Take(maxLength).ToArray());// use LINQ and be happy
    }

La ToArray()llamada aquí es simplemente innecesaria sobrecarga; usando, por ejemplo String.Concat, puede construir una cadena a partir de un número de caracteres enumerables sin tener que pasar por una matriz.
Mark Amery el

-3

Truncar cadena

public static string TruncateText(string strText, int intLength)
{
    if (!(string.IsNullOrEmpty(strText)))
    {                                
        // split the text.
        var words = strText.Split(' ');

        // calculate the number of words
        // based on the provided characters length 
        // use an average of 7.6 chars per word.
        int wordLength = Convert.ToInt32(Math.Ceiling(intLength / 7.6));

        // if the text is shorter than the length,
        // display the text without changing it.
        if (words.Length <= wordLength)
            return strText.Trim();                

        // put together a shorter text
        // based on the number of words
        return string.Join(" ", words.Take(wordLength)) + " ...".Trim();
    }
        else
        {
            return "";
        }            
    }

Esto no responde a la pregunta del OP. Primero, debería ser una función miembro (aunque la haya escrito como un método de extensión). En segundo lugar, el OP no especifica que el texto debe dividirse y las palabras se truncan a aprox. 7.6 caracteres por palabra.
Wicher Visser

7.6 es solo un número. Puedes escribir cualquier otro número que desees. Esto resultó ser una longitud promedio de palabras en inglés. Lo encontré en Google. Usar dividir es solo una manera fácil de dividir las palabras por espacio. ¡No creo que quieras mostrar media palabra! Por lo tanto, a menos que busque un espacio vacío que requiera más código, esta es una manera fácil de truncar una cadena y mostrar palabras completas. Esto asegurará que una cadena no sea más larga que la longitud dada y no tendrá palabras quebradas.
VT

-4

Este es el código que suelo usar:

string getSubString(string value, int index, int length)
        {
            if (string.IsNullOrEmpty(value) || value.Length <= length)
            {
                return value;
            }
            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            for (int i = index; i < length; i++)
            {
                sb.AppendLine(value[i].ToString());
            }
            return sb.ToString();
        }

55
Tenga en cuenta que concatenar cadenas con + = es una operación costosa, especialmente cuando se reconstruye carácter por carácter. Las cadenas .NET son inmutables, lo que significa que en este caso, se crea una nueva cadena cada vez en su ciclo.
Steve Guidi

Las cadenas @SteveGuidi no son inmutables, solo se hacen pasar por inmutables. Desearía que las cadenas fueran verdaderas primitivas inmutables para poder tener cadena y cadena ?, pero lamentablemente no son primitivas.
Chris Marisic

Dices caro como si el costo de rendimiento fuera significativo, lo cambié para usar stringBuilder pero encuentro que con + = es más fácil ver lo que está sucediendo, solo quería que el OP entienda fácilmente el código.
user3390116
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.