Como truncar uma string .NET?


406

Gostaria de truncar uma string de forma que seu comprimento não exceda um determinado valor. Estou escrevendo em uma tabela de banco de dados e quero garantir que os valores que eu escrevo atendam às restrições do tipo de dados da coluna.

Por exemplo, seria bom se eu pudesse escrever o seguinte:

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

Infelizmente, isso gera uma exceção, porque maxLengthgeralmente excede os limites da string value. Claro, eu poderia escrever uma função como a seguinte, mas esperava que algo assim já existisse.

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

Onde está a API indescritível que executa essa tarefa? Existe um?


24
Para o registro, as strings são imutáveis, você não pode truncá-las, você só pode retornar uma cópia truncada delas. Nitpicky, eu sei.
John Weldon

2
@ John Weldon: Provavelmente é por isso que a função de membro não existe - ela não segue a semântica do tipo de dados. Em uma nota lateral, StringBuilderpermite truncar encurtando o comprimento, mas você ainda precisa executar a verificação do comprimento para evitar o alargamento da corda.
Steve Guidi 5/05

11
Qualquer que seja a solução escolhida, adicione uma verificação de uma cadeia nula antes de chamar Substring ou acessar a propriedade Length.
Ray

3
@SteveGuidi - Se fosse esse o caso, então não haveria funções como guarnição ou Substituir, que enfrentam problemas semânticos semelhantes
Chris Rogers

11
@JohnWeldon Mais detalhista do que a própria Microsoft, consistentemente, é o que acontece - eles ficam felizes em documentar, por exemplo, .Trim()de uma maneira que pareça enganosa, pois muda a string: "Remove todos os caracteres de espaço em branco iniciais e finais do objeto String atual. "
Mark Amery

Respostas:


620

Truncate()Infelizmente, não há um método na string. Você precisa escrever esse tipo de lógica. O que você pode fazer, no entanto, é agrupar isso em um método de extensão para que você não precise duplicá-lo em qualquer lugar:

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); 
    }
}

Agora podemos escrever:

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

5
Ótima solução, mas lembre-se de que isso só funciona no NET 3.5 ou superior. Não tente no NET2.0.
Jedi Master Spooky

7
Enquanto você estiver no VS 2008 e, presumivelmente, no VS 2010, ainda poderá fazer isso, mesmo tendo como alvo o .Net 2.0. danielmoth.com/Blog/...
Mark

4
Isso falhará quando maxLengthfor um valor negativo.
22713 Bernard Bernard

42
@ Bernard, isso deve falhar se maxLength for negativo. Qualquer outro comportamento seria inesperado.
bojingo

12
Você pode chamar métodos de extensão com valores nulos.
Joel Malone

127

Ou, em vez do operador ternário, você pode 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! E a seguinte expressão é otimizado para retornar uma referência ao string original: value.Substring(0, value.Length).
Steve Guidi

4
Infelizmente, não é otimizado para casos em que value.Length é menor que MaxLength, o que pode ser um caso comum em alguns dados. Também a propriedade Length na string deve ser maiúscula.
jpierson

11
Isso falhará quando maxLengthfor um valor negativo.
27513 Bernard Bernard

7
@ Bernard, o mesmo acontecerá com muitas coisas no quadro ... mas se eu verificar ... eu tenho que usar maxLengthcomo padrão 0ou value.Length; ou eu preciso jogar um ArgumentOutOfRangeException... o que faz mais sentido nesse caso, e já é jogado de Substringqualquer maneira.
CaffGeek 3/15/13

2
Um pouco mais curto:return string.IsNullOrEmpty(value) ? value : value.Substring(0, Math.Min(value.Length, maxLength));
user1127860

43

Imaginei que lançaria minha implementação, pois acredito que ela cobre todos os casos que foram abordados pelos outros e o faz de uma maneira concisa que ainda é legível.

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

    return value;
}

Essa solução baseia-se principalmente na solução de Ray e abre o método para uso como método de extensão, usando a palavra - chave this, assim como LBushkin em sua solução.


Isso falhará quando maxLengthfor um valor negativo.
27513 Bernard Bernard

15
@ Bernard - Eu recomendaria não passar um valor negativo para o argumento maxLength, pois é um valor inesperado. O método Substring segue a mesma abordagem, portanto, não há razão para aprimorar a exceção lançada.
jpierson

Não acho que a verificação IsNullOrEmpty seja necessária? (1) Se o valor for nulo, não deverá haver como chamar esse método de extensão. (2) Se value for a string vazia, a verificação value.Length> maxLength falhará.
Jon Schneider

8
@ JonSchneider, o IsNullOrEmpty é necessário porque este é um método de extensão. Se você tiver uma variável do tipo string atribuída a um valor nulo, o compilador não insere uma verificação nula antes de chamar esse método. Tecnicamente, esse ainda é um método estático da classe estática. Então: stringVar.Truncate (2) Compila como: StringExt.Truncate (stringVar, 2);
Jeff B

40

Como o teste de desempenho é divertido: (usando métodos de extensão 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();

O truncatemétodo foi "significativamente" mais rápido. #microoptimization

Cedo

  • truncate10 5788 carrapatos decorridos (0,5788 ms) [em 10K repetições, 5.788E-05 ms por]
  • ticks de 8206 smart-trunc10 decorridos (0,8206 ms) [em 10K repetições, 8,206E-05 ms por]
  • stringbuilder10 10557 pontos decorridos (1.0557 ms) [em 10K repetições, 0,00010557 ms por]
  • concat10 45495 tiques decorridos (4.5495 ms) [em 10K repetições, 0,00045495 ms por]
  • newstring10 72535 tiques decorridos (7,2535 ms) [em 10K repetições, 0,00072535 ms por]

Atrasado

  • truncate44 8835 tiques decorridos (0,8835 ms) [em 10K repetições, 8,835E-05 ms por]
  • stringbuilder44 13106 pontos decorridos (1.3106 ms) [em 10K repetições, 0,00013106 ms por]
  • ticks 14821 smart-trunc44 decorridos (1,4821 ms) [em 10K repetições, 0,00014821 ms por]
  • newstring44 144324 pontos decorridos (14,4324 ms) [em 10K repetições, 0,00144324 ms por]
  • concat44 174610 tiques decorridos (17,461 ms) [em 10K repetições, 0,0017461 ms por]

Demasiado longo

  • tiquetaques smart-trunc64 6944 decorridos (0,6944 ms) [em 10K repetições, 6,944E-05 ms por]
  • truncate64 7686 tiques decorridos (0,7686 ms) [em 10K repetições, 7,686E-05 ms por]
  • stringbuilder64 13314 carrapatos decorridos (1,3314 ms) [em 10K repetições, 0,00013314 ms por]
  • newstring64 177481 tiques decorridos (17.7481 ms) [em 10K repetições, 0,00177481 ms por]
  • concat64 241601 ticks decorridos (24.1601 ms) [em 10K repetições, 0,00241601 ms por]

Obrigado por todos os benchmarks úteis! ... e o Linkpad arrasa!
Sunsetquest

nunca se importou que LINQPad poderia fazer essas coisas
jefissu

38

No .NET 4.0, você pode usar o Takemétodo:

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

Não testado quanto à eficiência!


27

Você pode usar o LINQ ... elimina a necessidade de verificar o comprimento da string. É certo que talvez não seja o mais eficiente, mas é divertido.

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

ou

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

2
por que essa não é a resposta aceita? O que é mais para a frente, escrever o seu próprio método de extensão que você precisa para manter / documento ou usando algo construído em como .Pegue
Don Cheadle

9
@mmcrae Linq pode ser mais direto, mas também é muito mais lento. Meu benchmark diz ~ 400ms para Linq e apenas ~ 24ms para Substring por 1 milhão de iterações.
Hein Andre Grønnestad

Esta solução nunca deve ser usada. Como dito nos dois comentários acima, sempre há alocação de memória, mesmo quando a string existente não é maior que o comprimento máximo. Também é muito lento.
Kamarey 27/02

15

Eu fiz o meu em uma linha como esta

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

2
-1; isso não adiciona nada que ainda não estava na resposta aceita.
Mark Amery

2
@markamery é uma alternativa mais curta, com menos código para escrever e atualizar quando você precisar usá-lo. Não gosta disso? Não usá-lo
SeanMC

Rápido, simples e rápido. Era disso que eu precisava. Obrigado!
Peter

14

Parece que ninguém postou isso ainda:

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

O uso do operador && o torna marginalmente melhor que a resposta aceita.


13

O .NET Framework possui uma API para truncar uma string como esta:

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

Mas, em um aplicativo C #, você provavelmente prefere usar o Microsoft.VisualBasic.dll, cuja principal razão de ser é a compatibilidade com versões anteriores.


"O .NET Framework tem uma API" você está se contradizendo. Essa é uma API do VB.NET
Camilo Terevinto

9
@CamiloTerevinto - é uma API fornecida com o .NET Framework e pode ser chamada de qualquer idioma gerenciado.
Joe

11
A DLL do VB contém muitas coisas boas. Por que existem tantos c # devs contra isso?
Michael Z.

Infelizmente, não há suporte para o .NET Core. De fato, todos os Microsoft.VisualBasic.Stringsmódulos do .NET Core estão vazios .
Mark Amery

11
Embora eu concorde com o comentário de Joe, ainda não me sinto bem chamando algo específico para VB de outros idiomas. Se houver tanta coisa boa na "DLL do VB", por que não colocá-la em algum lugar compartilhado? Quem sabe o que a Microsoft fará com essas coisas amanhã? Apoio parada ou algo ..
Kamarey


6

Sei que essa é uma pergunta antiga, mas aqui está uma boa solução:

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);

Retorna: olá ...


@SarjanWebDev Esse caractere especial aparece como "." em cmd.exe
Neal Ehardt,

5

Uma variante semelhante ao operador de propagação nula do C # 6

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

Observe que estamos essencialmente verificando se valueé nulo duas vezes aqui.


5

Ainda não há método Truncar em 2016 para seqüências de caracteres C #. Mas - usando a sintaxe do C # 6.0:

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); 
  }
}

Ele funciona como um encanto:

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

4

Tomando o @CaffGeek e simplificando-o:

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

4

Observe que truncar uma string não significa apenas cortar uma string apenas com um comprimento especificado, mas deve-se tomar cuidado para não dividir a palavra.

eg string: esta é uma string de teste.

Eu quero cortar às 11. Se usarmos algum dos métodos acima, o resultado será

isso é um te

Não é isso que queremos

O método que estou usando também pode não ser tão perfeito, mas pode lidar com a maior parte da situação

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 que não:

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

ou seja, nos value.Length < maxLengthespaços do bloco de eventos até o fim ou truncar o excesso.


Você gera o dobro de objetos de seqüência de caracteres e pode gerar uma NullReferenceException da chamada PadRight, se o valor for nulo, o que é inadequado, deve ser uma ArgumentNullException.
Jeremy

11
@ Jeremy Eu não entendo "ele poderia lançar uma NullReferenceException da chamada PadRight se o valor for nulo"; eu não mencionei "// verifique a string.IsNullOrEmpty (value) e aja de acordo com ela."
Sri

3

Apenas no caso de não haver respostas suficientes aqui, aqui está a minha :)

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

Por uma questão de (excesso) complexidade, adicionarei minha versão sobrecarregada que substitui os últimos 3 caracteres por uma elipse em relação ao 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

Meus dois centavos com exemplo de comprimento de 30:

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

1

Prefiro a resposta de jpierson, mas nenhum dos exemplos aqui que posso ver está lidando com um parâmetro maxLength inválido, como quando maxLength <0.

As opções seriam manipular o erro em uma tentativa / captura, fixe o parâmetro maxLength min a 0 ou, se maxLength for menor que 0, retorne uma sequência vazia.

Código não otimizado:

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
Observe que, na minha implementação, optei por não lidar com o caso em que maximumLength é menor que 0 porque achei que a única coisa que faria seria lançar um ArgumentOutOfRangeExcpetion, que essencialmente o que string.Substring () faz por mim.
jpierson

1

Aqui está uma solução vb.net, marque que a instrução if (embora feia) melhora o desempenho, porque não precisamos da instrução substring quando a string já é menor que o comprimento máximo ... Ao torná-la uma extensão da string, é 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

No VB.net, você pode substituir "Not String.IsNullOrEmpty (String__1)" por "String__1 <> Nothing". É um pouco mais curto. O valor padrão para cadeias é uma cadeia vazia. O uso de "<> Nothing" verifica os casos de seqüência de caracteres nulos e vazios. Teste-o com: Truncar ("", 50) e Truncar (nada, 50)
jrjensen 15/05

Em VB você pode fazer Esquerda (string, maxlength)
Michael Z.

1

Eu sei que já existem muitas respostas, mas minha necessidade era manter intacto o início e o fim da string, mas encurtá-la para o comprimento máximo.

    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));
    }

Isso é para criar URLs do SharePoint com um comprimento máximo de 260 caracteres.

Eu não tornei o comprimento um parâmetro, pois é uma constante 260. Também não fiz o primeiro comprimento da substring um parâmetro porque quero que ele quebre em um ponto específico. Finalmente, a segunda substring é o comprimento da fonte - 20 desde que conheço a estrutura da pasta.

Isso pode ser facilmente adaptado às suas necessidades específicas.


1

Eu sei que já existem muitas respostas aqui, mas essa é a que eu tenho, que lida com cadeias nulas e a situação em que o comprimento passado é negativo:

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

No C # 8, o novo recurso Ranges pode ser usado ...

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

0

Não há nada no .net para isso que eu saiba - aqui está a minha versão que adiciona "...":

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
Sua versão fornecerá seqüências de caracteres com três caracteres a mais que o tamanho solicitado, caso estejam truncadas. Além disso, os pontos triplos são realmente apenas significativos na representação, eu não os armazenaria em um banco de dados como aquele que é o caso de uso que o OP forneceu.
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 que você precede o nome do método público com um sublinhado?
Michael Z.

0

Como complemento às possibilidades discutidas acima, eu gostaria de compartilhar minha solução. É um método de extensão que permite null (retorna string.Empty) e também existe um segundo .Truncate () para usá-lo com reticências. Cuidado, não é um desempenho otimizado.

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
    }

A ToArray()ligação aqui é apenas desnecessária; usando, por exemplo, String.Concatvocê pode construir uma string a partir de um enumerável de caracteres sem ter que passar por uma matriz.
Mark Amery

-3

Truncar String

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 "";
        }            
    }

Isso não responde à pergunta do OP. Primeiro, deve ser uma função de membro (embora você a tenha escrito como um método de extensão). Segundo, o OP não especifica que o texto precise ser dividido e que as palavras sejam truncadas para aprox. 7,6 caracteres por palavra.
Wicher Visser

7.6 é apenas um número. você pode escrever qualquer outro número que desejar. Por acaso, esse é o tamanho médio das palavras em inglês. Eu encontrei isso no google. Usar a divisão é apenas uma maneira fácil de dividir as palavras por espaço. Eu não acho que você queira exibir meia palavra! Portanto, a menos que você percorra para encontrar o espaço vazio que exigirá mais código, essa é uma maneira fácil de truncar uma string e exibir palavras completas. Isso garantirá que uma string não seja maior que o comprimento especificado e que você não terá palavras quebradas.
VT

-4

Este é o código que eu costumo 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();
        }

5
Observe que concatenar cadeias com + = é uma operação cara, especialmente ao reconstruir caractere por caractere. As strings do .NET são imutáveis, o que significa que, nesse caso, uma nova string é criada a cada vez em seu loop.
Steve Guidi

As strings do @SteveGuidi não são imutáveis, apenas se disfarçam de imutáveis. Eu gostaria que as strings fossem verdadeiras primitivas imutáveis ​​para que eu pudesse ter string e string ?, mas, infelizmente, elas não são primitivas.
Chris Marisic

Você diz caro, como se o custo de desempenho fosse significativo, eu mudei para usar stringBuilder, mas acho que com + = é mais fácil ver o que está acontecendo, eu só queria que o OP entendesse facilmente o código.
user3390116
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.