Как обрезать строку .NET?

406

Я хотел бы усечь строку так, чтобы ее длина не превышала заданное значение. Я пишу в таблицу базы данных и хочу убедиться, что значения, которые я пишу, соответствуют ограничению типа данных столбца.

Например, было бы хорошо, если бы я мог написать следующее:

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

К сожалению, это вызывает исключение, потому что maxLengthобычно превышает границы строки value. Конечно, я мог написать функцию, подобную следующей, но я надеялся, что нечто подобное уже существует.

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

Где неуловимый API, который выполняет эту задачу? Есть один?

Стив Гвиди
источник
24
Для записи, строки неизменны, вы не можете их усекать, вы можете только вернуть их усеченную копию. Nitpicky, я знаю.
Джон Уэлдон
2
@ Джон Уэлдон: Вероятно, поэтому функция-член не существует - она ​​не соответствует семантике типа данных. Что StringBuilderкасается примечания, позволяет обрезать путем сокращения длины, но вам все равно необходимо выполнить проверку длины, чтобы избежать расширения строки.
Стив Гуиди
1
Какое бы решение вы ни выбрали, обязательно добавьте проверку на пустую строку перед вызовом Substring или доступом к свойству Length.
Рэй
3
@SteveGuidi - Если бы это было так, то не было бы таких функций, как Trim или Replace, которые сталкивались бы с подобными семантическими проблемами
Крис Роджерс
1
@JohnWeldon Более придирчивы, чем сами Microsoft, как это бывает - они счастливы документировать, например, .Trim()таким образом, что это вводит в заблуждение, как будто мутирует строку: «Удаляет все начальные и конечные пробельные символы из текущий объект String. "
Марк Амери

Ответы:

620

К Truncate()сожалению, метода для строки не существует. Вы должны написать такую ​​логику самостоятельно. Однако вы можете заключить это в метод расширения, чтобы вам не приходилось дублировать его везде:

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

Теперь мы можем написать:

var someString = "...";
someString = someString.Truncate(2);
LBushkin
источник
5
Отличное решение, но помните, что это работает только в NET 3.5 и выше. Не пытайтесь сделать это в NET2.0.
Мастер-джедай Spooky
7
Пока вы находитесь в VS 2008 и, предположительно, в VS 2010, вы все равно могли бы делать это, даже ориентируясь на .Net 2.0. danielmoth.com/Blog/...
Mark
4
Это не удастся, когда maxLengthотрицательное значение.
Бернард
42
@ Бернард, это должно произойти, если maxLength отрицателен. Любое другое поведение будет неожиданным.
Bojingo
12
Вы можете вызывать методы расширения для нулевых значений.
Джоэл Мэлоун
127

Или вместо троичного оператора вы можете использовать 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));
    }
}
CaffGeek
источник
10
Умная! А следующее выражение оптимизировано для возвращения ссылки на исходную строку: value.Substring(0, value.Length).
Стив Гуиди
4
К сожалению, он не оптимизирован для случаев, когда value.Length меньше MaxLength, что может быть распространенным случаем в некоторых данных. Также свойство Length в строке должно быть написано заглавными буквами.
jpierson
1
Это не удастся, когда maxLengthотрицательное значение.
Бернард
7
@Bernard, так что будет много вещей , в рамках ... но если я проверить ... Я либо есть по умолчанию , maxLengthчтобы 0или value.Length; или мне нужно бросить ArgumentOutOfRangeException..., который имеет больше смысла в этом случае, и все равно уже брошен Substring.
CaffGeek
2
Немного короче:return string.IsNullOrEmpty(value) ? value : value.Substring(0, Math.Min(value.Length, maxLength));
user1127860 13.09.16
43

Я подумал, что добавлю свою реализацию, так как считаю, что она охватывает все случаи, которые были затронуты другими, и делает это в сжатой форме, которая все еще читаема.

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

    return value;
}

Это решение в основном опирается на решение Рэя и открывает метод для использования в качестве метода расширения путем использования этого ключевого слова так же , как LBushkin делает в своем решении.

jpierson
источник
Это не удастся, когда maxLengthотрицательное значение.
Бернард
15
@Bernard - я бы рекомендовал не передавать отрицательное значение для аргумента maxLength, так как это неожиданное значение. Метод Substring использует тот же подход, поэтому нет причин улучшать исключение, которое он выдает.
jpierson
Я не думаю, что проверка IsNullOrEmpty необходима? (1) Если значение равно нулю, у него не должно быть способа вызвать этот метод расширения. (2) Если значение является пустой строкой, проверка value.Length> maxLength завершится неудачей.
Джон Шнайдер
8
@JonSchneider, IsNullOrEmpty требуется, потому что это метод расширения. Если у вас есть переменная типа string, которой присвоено значение NULL, компилятор не вставляет проверку NULL перед вызовом этого метода. Технически, это все еще статический метод статического класса. Итак: stringVar.Truncate (2) Компилируется как: StringExt.Truncate (stringVar, 2);
Джефф Б
40

Потому что тестирование производительности это весело: (используя методы расширения 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();

truncateМетод был «значительно» быстрее. #microoptimization

Рано

  • truncate10 Прошло 5788 тиков (0,5788 мс) [за 10 000 повторений, 5,788E-05 мс за]
  • smart-trunc10 истекло 8206 тактов (0,8206 мс) [в 10 000 повторений, 8,206E-05 мс за]
  • stringbuilder10 Прошло 10557 тактов (1,0557 мс) [за 10 000 повторений, 0,00010557 мс за]
  • concat10 Прошло 45495 тактов (4,5495 мс) [в 10 000 повторений, 0,00045495 мс за]
  • newstring10 72535 тактов прошло (7,2535 мс) [в 10 000 повторений, 0,00072535 мс на]

поздно

  • truncate44 Прошло 8835 тактов (0,8835 мс) [в 10 000 повторений, 8,835E-05 мс за]
  • stringbuilder44 Прошло 13106 тактов (1,3106 мс) [за 10 000 повторений, 0,00013106 мс за]
  • smart-trunc44 Прошло 14821 тактов (1,4821 мс) [в 10 000 повторений, 0,00014821 мс за]
  • newstring44 144324 тактов прошло (14,4324 мс) [за 10 000 повторений, 0,00144324 мс за]
  • concat44 Прошло 174610 тиков (17,461 мс) [за 10 тысяч повторений, 0,0017461 мс за]

Слишком долго

  • smart-trunc64 Прошло 6944 такта (0,6944 мс) [за 10 000 повторений, 6,944E-05 мс за]
  • truncate64 Прошло 7686 тактов (0,7686 мс) [за 10 000 повторений, 7,686E-05 мс за]
  • stringbuilder64 Прошло 13314 тактов (1,3314 мс) [в 10 000 повторений, 0,00013314 мс за]
  • newstring64 177481 тактов прошло (17.7481 мс) [за 10 000 повторений, 0.00177481 мс за]
  • concat64 Прошло 241601 тактов (24,1601 мс) [за 10 000 повторений, 0,00241601 мс за]
drzaus
источник
Спасибо за все полезные тесты! ... и Linkpad качается!
Sunsetquest
никогда не думал, что linqpad может делать такие вещи
jefissu
38

В .NET 4.0 вы можете использовать Takeметод:

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

Не проверено на эффективность!

Дилан Николсон
источник
27

Вы можете использовать LINQ ... это избавляет от необходимости проверять длину строки. Правда, возможно, не самый эффективный, но это весело.

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

или

string result = new string(value.Take(maxLength).ToArray());
приручает
источник
2
почему это не принятый ответ? Что самое простое, написать свой собственный метод Extension, который вам нужно поддерживать / документировать, или использовать что-то, как BUILT IN, например .Take
Don Cheadle
9
@mmcrae Linq может быть более прямым, но и намного медленнее. Мой тест говорит о ~ 400 мс для Linq и всего ~ 24 мс для подстроки на 1 миллион итераций.
Hein Andre Grønnestad
Это решение никогда не должно использоваться. Как сказано в двух вышеупомянутых комментариях, всегда есть выделение памяти, даже когда существующая строка не превышает максимальную длину. Также это очень медленно.
Камари
15

Я сделал мой в одной строке вроде этого

value = value.Length > 1000 ? value.Substring(0, 1000) : value;
SeanMC
источник
2
-1; это не добавляет ничего, чего не было в принятом ответе.
Марк Эмери
2
@markamery - это более короткая альтернатива с меньшим количеством кода для написания и обновления, когда вам нужно его использовать. Не нравится это? Не используйте его
SeanMC
Быстро, просто и быстро. Это то, что мне было нужно. Спасибо!
Питер
14

Кажется, никто еще не опубликовал это:

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

Использование оператора && делает его немного лучше, чем принятый ответ.

Даррен
источник
13

.NET Framework имеет API для усечения строки, подобной этой:

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

Но в приложении на C # вы, вероятно, предпочтете свернуть свое собственное, чем брать зависимость от Microsoft.VisualBasic.dll, основным смыслом которой является обратная совместимость.

Джо
источник
«.NET Framework имеет API», вы сами себе противоречите. Это API VB.NET
Камило Теревинто,
9
@CamiloTerevinto - это API, который поставляется с .NET Framework и может вызываться из любого управляемого языка.
Джо
1
В VB DLL есть много хорошего. Почему так много разработчиков C # против?
Майкл З.
К сожалению, в настоящее время нет поддержки .NET Core. Действительно, все Microsoft.VisualBasic.Stringsмодули в .NET Core довольно пусты .
Марк Амери
1
Хотя я согласен с комментарием Джо, все же я не чувствую себя правильным, называя что-то конкретное для VB с других языков. Если в «VB DLL» так много хороших вещей, почему бы не поместить их в какое-то общее место? Кто знает, что Microsoft будет делать с этими вещами завтра? Прекратить поддержку или что-то ..
Kamarey
12

Другое решение:

return input.Substring(0, Math.Min(input.Length, maxLength));
Марек Мальчевский
источник
6

Я знаю, что это старый вопрос, но вот хорошее решение:

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

Возвращает: привет ...

NOLOGO
источник
@SarjanWebDev Этот специальный символ отображается как "." в cmd.exe
Нил Эхардт
5

Аналогичный вариант с оператором распространения C # 6 Null

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

Пожалуйста, обратите внимание, что мы по существу проверяем, valueявляется ли значение null здесь дважды.

Джейми Рис
источник
5

По-прежнему нет метода усечения в 2016 году для строк C #. Но - используя синтаксис 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); 
  }
}

Работает как часы:

"Truncate me".Truncate(8);
Result: "Truncate"
Тобиас Шиле
источник
4

Взяв @CaffGeek и упростив его:

public static string Truncate(this string value, int maxLength)
    {
        return string.IsNullOrEmpty(value) ? value : value.Substring(0, Math.Min(value.Length, maxLength));
    }
Эдвин Белтран
источник
4

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

например, строка: это тестовая строка.

Я хочу сократить это в 11. Если мы используем любой из методов, указанных выше, результат будет

это т

Это не то, что мы хотим

Метод, который я использую, также может быть не таким идеальным, но он может справиться с большинством ситуаций.

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

Почему бы нет:

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

то есть в value.Length < maxLengthпробелах места для пробелов до конца или усекать лишнее.

Шри
источник
Вы генерируете в два раза больше строковых объектов, и это может вызвать исключение NullReferenceException из вызова PadRight, если значение равно null, что неуместно, это должно быть ArgumentNullException.
Джереми
1
@ Джереми, я не понимаю, «это может вызвать исключение NullReferenceException из вызова PadRight, если значение равно нулю»; Разве я не упомянул "// проверить строку. IsNullOrEmpty (значение) и действовать на него".
Sri
3

На всякий случай, если здесь недостаточно ответов, вот мой :)

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

использовать:

"I use it like this".Truncate(5,"~")
KR
источник
2

Ради (чрезмерной) сложности я добавлю свою перегруженную версию, которая заменяет последние 3 символа многоточием по отношению к параметру 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)); 
}
SoftDev
источник
2

Мои два цента с примером длины 30:

  var truncatedInput = string.IsNullOrEmpty(input) ? 
      string.Empty : 
      input.Substring(0, Math.Min(input.Length, 30));
Огнян Димитров
источник
1

Я предпочитаю ответ jpierson, но ни один из приведенных здесь примеров не обрабатывает недопустимый параметр maxLength, например, когда maxLength <0.

Выбор будет либо обрабатывать ошибку в try / catch, ограничивать параметр maxLength min до 0, либо, если maxLength меньше 0, возвращать пустую строку.

Неоптимизированный код:

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;
}
deegee
источник
3
Обратите внимание, что в моей реализации я решил не обрабатывать случай, когда MaximumLength меньше 0, потому что я решил, что единственное, что я хотел бы сделать, это бросить ArgumentOutOfRangeExcpetion, который, по сути, делает для меня string.Substring ().
jpierson
1

Вот решение vb.net, отметьте, что оператор if (хотя и уродливый) повышает производительность, потому что нам не нужен оператор substring, когда строка уже меньше maxlength ... Делая это расширением строки, его легко использовать. ..

 <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
Йерун Бом
источник
В VB.net вы можете заменить «Not String.IsNullOrEmpty (String__1)» на «String__1 <> Nothing». Это немного короче. Значением по умолчанию для строк является пустая строка. Использование «<> Nothing» проверяет как нулевые, так и пустые строковые значения. Проверьте это с помощью: усечения ("", 50) и усечения (ничего, 50)
jrjensen
В VB вы можете сделать левый (строка, максимальная длина)
Майкл З.
1

Я знаю, что уже есть тонна ответов, но мне нужно было сохранить начало и конец строки без изменений, но сократить их до максимальной длины.

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

Это предназначено для создания URL-адресов SharePoint длиной не более 260 символов.

Я не сделал длину параметром, так как она является постоянной величиной 260. Я также не сделал первую длину подстроки параметром, потому что я хочу, чтобы она ломалась в определенной точке. Наконец, вторая подстрока - это длина источника - 20, поскольку я знаю структуру папок.

Это может быть легко адаптировано к вашим конкретным потребностям.

Пол Хаан
источник
1

Я знаю, что здесь уже есть тонна ответов, но это тот, с которым я ушел, который обрабатывает как нулевые строки, так и ситуацию, когда передаваемая длина отрицательна:

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

В C # 8 может быть использована новая функция диапазонов ...

value = value[..Math.Min(30, value.Length)];
Sunsetquest
источник
0

В этом нет ничего, что мне известно, - вот моя версия, которая добавляет «...»:

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
Ваша версия будет выдавать строки, которые на 3 символа длиннее запрошенной длины, в случае, если они усекаются. Кроме того, тройные точки действительно просто значимы в представлении, я бы не стал хранить их в базе данных, подобной той, которая используется в 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;
}
Sud
источник
2
Почему перед вашим публичным именем метода стоит знак подчеркивания?
Майкл З.
0

В дополнение к рассмотренным выше возможностям я хотел бы поделиться своим решением. Это метод расширения, который позволяет null (возвращает string.Empty), а также есть второй .Truncate () для его использования с многоточием. Осторожно, это не оптимизированная производительность.

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
    }
TapiocaCom
источник
ToArray()Вызов здесь просто само собой накладные расходы; используя, например, String.Concatвы можете создать строку из множества символов без необходимости проходить через массив.
Марк Эмери
-3

Усеченная строка

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 "";
        }            
    }
Вермонт
источник
Это не отвечает на вопрос ОП. Во-первых, это должна быть функция-член (хотя вы написали это как метод расширения). Во-вторых, OP не указывает, что текст должен быть разделен, а слова должны быть усечены до приблиз. 7,6 символа за слово.
Wicher Visser
7,6 это просто число. Вы можете написать любой другой номер, который вы хотите. Это оказалось средней длины английского слова. Я нашел это в Google. Использование split - это простой способ разбить слова на пробелы. Я не думаю, что вы хотите отобразить половину слова! Таким образом, если вы не перебираете, чтобы найти пустое пространство, которое потребует больше кода, это простой способ обрезать строку и отображать полные слова. Это гарантирует, что строка не длиннее заданной длины, и у вас не будет разбитых слов.
VT
-4

Это код, который я обычно использую:

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();
        }
user3390116
источник
5
Обратите внимание, что объединение строк с + = является дорогостоящей операцией, особенно при восстановлении символа за символом. Строки .NET являются неизменяемыми, что означает, что в этом случае новая строка создается каждый раз в вашем цикле.
Стив Гуиди,
Строки @SteveGuidi не являются неизменяемыми, они просто маскируются как неизменные. Я бы хотел, чтобы строки были настоящими неизменяемыми примитивами, чтобы я мог иметь строку и строку ?, но, увы, они не являются примитивами.
Крис Марисик
Вы говорите «дорого», как будто затраты на производительность значительны, я изменил его на использование stringBuilder, но я обнаружил, что с + = легче увидеть, что происходит, я просто хотел, чтобы OP легко понимал код.
user3390116