Як обрізати рядок .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
Для запису, рядки є незмінними, ви не можете їх усікати, ви можете повернути лише усічену їх копію. Нітпікі, я знаю.
Джон Велдон

2
@John Weldon: Мабуть, тому функція-член не існує - вона не відповідає семантиці типу даних. Зі сторони нота StringBuilderдозволяє врізати, скорочуючи довжину, але все одно потрібно перевірити довжину, щоб уникнути розширення рядка.
Стів Гуїді

1
Яке б рішення ви не вибрали, не забудьте додати чек на нульову рядок, перш ніж викликати Substring або отримати доступ до властивості Length.
Рей

3
@SteveGuidi - Якби це було так, тоді не було б таких функцій, як Trim або Replace, які стикаються з подібними семантичними проблемами
Chris Rogers

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

5
Прекрасне рішення, але пам'ятайте, що це працює лише в NET 3.5 і вище. Не спробуйте в NET2.0.
Майстер джедаїв Моторошний

7
Поки ви знаходитесь у VS 2008 та, імовірно, VS 2010, ви все одно можете це робити, навіть якщо орієнтуєтесь на .Net 2.0. danielmoth.com/Blog/…
Марк

4
Це не вдасться, коли maxLengthце від'ємне значення.
Бернард

42
@ Бернарда, це, мабуть, не вдасться, якщо maxLength негативний. Будь-яка інша поведінка була б несподіваною.
боїнго

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

10
Розумний! А такий вираз оптимізовано для повернення посилання на вихідну рядок: value.Substring(0, value.Length).
Стів Гуїді

4
На жаль, він не оптимізований для випадків, коли value.Length менше MaxLength, що може бути звичайним випадком у деяких даних. Також властивість Length у рядку має бути написано з великої літери.
jpierson

1
Це не вдасться, коли maxLengthце від'ємне значення.
Бернард

7
@ Berernard, так це буде багато речей у рамках ... але якщо я перевіряю це ... я або маю за замовчуванням maxLengthдо 0або value.Length; або мені потрібно кинути ArgumentOutOfRangeException..., що має більше сенсу в даному випадку, і його вже так Substringчи інакше кинули .
CaffGeek

2
Трохи коротше:return string.IsNullOrEmpty(value) ? value : value.Substring(0, Math.Min(value.Length, maxLength));
user1127860

43

Я вважав, що буду втілюватись у своїй реалізації, оскільки я вважаю, що вона охоплює всі випадки, які торкнулися інші, і робить це стислим способом, який ще читається.

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

    return value;
}

Це рішення головним чином спирається на рішення Рея і відкриває метод для використання як метод розширення, використовуючи це ключове слово так само, як це робить Л.Бушкін у своєму рішенні.


Це не вдасться, коли maxLengthце від'ємне значення.
Бернард

15
@Bernard - Я рекомендую не передавати негативне значення для аргументу maxLength, оскільки це несподіване значення. Метод Substring застосовує той самий підхід, тому немає ніяких причин вдосконалюватись за винятком, який він кидає.
jpierson

Я не думаю, що перевірка IsNullOrEmpty необхідна? (1) Якщо значення є нульовим, не повинно бути способом викликати цей метод розширення на ньому. (2) Якщо значення порожній рядок, значення value.Length> maxLength перевірка не вдасться.
Джон Шнайдер

8
@JonSchneider, IsNullOrEmpty необхідний, оскільки це метод розширення. Якщо у вас є змінна строка типу, якій було призначено нуль, компілятор не вставляє перевірку нуля перед викликом цього методу. Технічно це все-таки статичний метод статичного класу. Отже: 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Метод був «значно» швидше. #мікрооптимізація

Рано

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

Пізно

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

Надто довго

  • минуло 6944 кліщів smart-trunc64 (0,6944 мс) [у 10 К повторень, 6,944E-05 мс на]
  • truncate64 7686 кліщів (0,7686 мс) [у 10 К повторень, 7,686E-05 мс на]
  • stringbuilder64 минуло 13314 тиків (1,3314 мс) [у 10 К повторень, 0,00013314 мс на]
  • newstring64 минуло 177481 кліщів (17,7481 мс) [у 10 К повторень, 0,00177481 мс на]
  • concat64 241601 кліщів минуло (24.1601 мс) [у 10 К повторень, 0.00241601 мс на]

Дякуємо за всі корисні орієнтири! ... і Лінкпад скелі!
Захід сонця

ніколи не пам'ятав, що 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
чому це не прийнята відповідь? Що найбільш прямо вперед, писати свій власний метод розширення , що вам необхідно для підтримки / документ, або використовуючи що - щось на зразок побудований в .Снять
Дон Чідл

9
@mmcrae Linq може бути більш прямим вперед, але це також набагато повільніше. Мій показник говорить ~ 400 мс для Linq і лише ~ 24 мс для підрядкової стрічки для 1 млн ітерацій.
Хайн Андре Гріннестад

Це рішення ніколи не слід використовувати. Як сказано в двох вище коментарях, завжди є розподіл пам’яті, навіть коли існуюча рядок не перевищує максимальну довжину. Також це дуже повільно.
Камарей

15

Я робив своє в одному рядку, як це

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

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
У DLL VB є багато хороших речей. Чому стільки c # devs проти цього?
Michael Z.

На жаль, на даний момент не підтримується .NET Core. Дійсно, цілі Microsoft.VisualBasic.Stringsмодулі в .NET Core досить порожні .
Марк Амері

1
Хоча я погоджуюся з коментарем Джо, я все ще не вважаю правильним називати щось специфічне для VB з інших мов. Якщо у "VB DLL" є стільки хороших речей, чому б не помістити її в якесь спільне місце? Хто знає, що Microsoft зробить із цими матеріалами завтра? Припиніть підтримку чи щось ..
Камарей


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

Повертається: привіт ...


@SarjanWebDev Цей спеціальний символ відображається як "." in cmd.exe
Ніл Ехардт

5

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

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

Зверніть увагу, ми по суті перевіряємо, чи valueє нуль двічі тут.


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, якщо значення є нульовим, що є невідповідним, це повинно бути ArgumentNullException.
Джеремі

1
@Jeremy я не розумію, що "це може викинути NullReferenceException з виклику PadRight, якщо значення є нульовим"; чи я не згадав "// check string.IsNullOrEmpty (значення) та діяти по ньому."
Шрі

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,"~")

2

Заради (над) складності я додам свою перевантажену версію, яка замінює останні три символи еліпсісом щодо параметра 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

Мої два центи з прикладом довжини 30:

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

1

Я вважаю за краще відповідь jpierson, але жоден із наведених тут прикладів не обробляє недійсний параметр maxLength, наприклад, коли maxLength <0.

Вибір буде або обробляти помилку в спробі / ловити, затиснути параметр 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;
}

3
Зауважте, що в моєму виконанні я вирішив не обробляти випадок, коли максимальний розмір менше 0, тому що я зрозумів, що єдине, що я повинен зробити, це кинути ArgumentOutOfRangeExcpetion, який по суті є те, що string.Substring () робить для мене.
jpierson

1

Ось рішення vb.net, зазначте, що оператор if (хоча і некрасивий) покращує продуктивність, оскільки нам не потрібен оператор підрядки, коли рядок вже менший 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 <> Нічого". Це трохи коротше. Типовим значенням для рядків є порожній рядок. Використовуючи "<> Нічого", перевіряє і нульові, і порожні рядкові рядки. Перевірте це: Truncate ("", 50) та Truncate (Nothing, 50)
jrjensen

У VB можна обійтися зліва (рядок, максимальна довжина)
Michael Z.

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 символів.

Я не зробив параметр length, оскільки він є постійним 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)];

0

Для цього немає нічого в .net, про що я знаю - ось моя версія, яка додає "...":

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 символи довше потрібної довжини, якщо вони усічені. Крім того, потрійні точки дійсно мають значення лише у представленні, я б не зберігав їх у такій базі даних, як та, яка є випадком використання, який дала ОП.
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
Чому ви передуєте назві вашого загальнодоступного методу підкресленням?
Майкл З.

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
    }

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

Це не відповідає на питання ОП. По-перше, це повинна бути функція-член (хоча ви написали це як метод розширення). По-друге, в ОП не визначено, що текст повинен бути розбитим, а слова - усіченими до ок. 7,6 символів за слово.
Вічер Вісер

7.6 - це просто число. ви можете написати будь-який інший номер, який хочете. Це сталося середньої довжини англійських слів. Я знайшов це в Google. Використання розділення - просто простий спосіб розділити слова на пробіл. Я не думаю, що ти хочеш показати пів слова! Тож якщо ви не переглянете, щоб знайти порожній простір, для якого знадобиться більше коду, це простий спосіб урізати рядок і відобразити повноцінні слова. Це забезпечить вам, що рядок не буде більше заданої довжини і у вас не буде ламаних слів.
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();
        }

5
Зверніть увагу, що об'єднання рядків з + = - це дорога операція, особливо при перебудові символів за символом. Рядок .NET є незмінним, що означає, що в цьому випадку створюється нова рядок щоразу у вашому циклі.
Стів Гуїді

@SteveGuidi рядки не є незмінними, вони просто маскуються як непорушні. Я б хотів, щоб струни були справжніми незмінними примітивами, щоб я міг мати рядок і рядок ?, але, на жаль, вони не примітиви.
Кріс Марісіч

Ви говорите дорого, ніби вартість продуктивності значна, я змінив його на stringBuilder, але я вважаю, що з + = простіше зрозуміти, що відбувається, я просто хотів, щоб ОП легко зрозумів код.
користувач3390116
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.