如何截断.NET字符串?


406

我想截断一个字符串,使其长度不超过给定值。我正在写数据库表,并希望确保所写的值满足列数据类型的约束。

例如,如果我可以编写以下代码,那就太好了:

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

不幸的是,这引起了一个异常,因为maxLength通常超出了string的边界value。当然,我可以编写类似以下的函数,但我希望这样的东西已经存在。

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

执行此任务的难以捉摸的API在哪里?有一个吗?


24
为了记录起见,字符串是不可变的,您不能截断它们,而只能返回它们的截断副本。Nitpicky,我知道。
约翰·韦尔登

2
@John Weldon:这可能是成员函数不存在的原因-它不遵循数据类型的语义。附带说明一下,StringBuilder可以通过缩短长度来进行截断,但是仍然需要执行长度检查以避免加宽字符串。
史蒂夫·吉迪

1
无论选择哪种解决方案,请务必在调用Substring或访问Length属性之前添加对空字符串的检查。

3
@SteveGuidi-如果是这种情况,那么就不会像Trim或Replace这样的函数面临类似的语义问题
Chris Rogers

1
@JohnWeldon碰巧比Microsoft本身更挑剔-例如,他们很乐意以.Trim()某种方式记录下来,使其听起来像是对字符串进行了突变:“当前的String对象。”
Mark Amery

Answers:


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中尝试。
Jedi Master Spooky

7
只要您使用的是VS 2008,大概是VS 2010,即使以.Net 2.0为目标,您仍然可以执行此操作。danielmoth.com/Blog/…– 2010
标记为

4
maxLength为负值时,这将失败。
伯纳德

42
@Bernard,如果maxLength为负数,这应该会失败。任何其他行为都是意外的。
bojingo 2014年

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 2012年

1
maxLength为负值时,这将失败。
伯纳德

7
@Bernard,框架中的很多内容也是如此...但是如果我检查...我要么必须默认maxLength0要么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;
}

该解决方案主要基于Ray的解决方案,并像LBushkin在其解决方案中一样,通过使用this关键字,将其用作扩展方法。


maxLength为负值时,这将失败。
伯纳德

15
@Bernard-我建议不要为maxLength参数传递负值,因为它是意外值。Substring方法采用相同的方法,因此没有理由改进它引发的异常。
jpierson

我认为IsNullOrEmpty检查不是必需的吗?(1)如果value为null,则不应在其上调用此扩展方法。(2)如果value为空字符串,则value.Length> maxLength检查将失败。
乔恩·施耐德

8
@JonSchneider,需要IsNullOrEmpty,因为这是扩展方法。如果您为字符串类型的变量分配了null,则在调用此方法之前,编译器不会插入null检查。从技术上讲,这仍然是静态类的静态方法。因此:stringVar.Truncate(2)编译为:StringExt.Truncate(stringVar,2);
杰夫·B

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 ms)[以1万次重复,每个5.788E-05 ms]
  • smart-trunc10过去了8206个滴答声(0.8206毫秒)[以10K代表,每个8.206E-05毫秒]
  • stringbuilder10经过10557个滴答声(1.0557 ms)[以10K代表,每个0.00010557 ms]
  • concat10经过45495个滴答声(4.5495 ms)[以1万次代表,每个0.00045495 ms]
  • newstring10经过72535个滴答声(7.2535毫秒)[以1万次代表,每个0.00072535毫秒]

晚了

  • truncate44经过8835个滴答声(0.8835毫秒)[以10K代表,每个8.835E-05毫秒]
  • stringbuilder44经过13106个滴答声(1.3106毫秒)[以1万次代表,每个0.00013106毫秒]
  • smart-trunc44经过14821个滴答声(1.4821 ms)[以10K代表,每个0.00014821 ms]
  • newstring44经过144324个滴答声(14.4324 ms)[以10K代表,每个0.00144324 ms]
  • concat44 174610滴答已过去(17.461毫秒)[以1万次代表,每0.0017461毫秒]

太长

  • smart-trunc64经过6944个滴答声(0.6944毫秒)[以10K代表,每个6.944E-05毫秒]
  • truncate64经过7686次滴答(0.7686毫秒)[以1万次重复,每7.686E-05毫秒]
  • stringbuilder64经过13314滴答声(1.3314毫秒)[以10K代表,每0.00013314毫秒]
  • newstring64经过177481个滴答声(17.7481毫秒)[以1万次代表,每个0.00177481毫秒]
  • concat64 241601滴答已过去(24.1601 ms)[以1万次代表,每0.00241601 ms]

感谢所有有用的基准!...和Linkpad震撼!
Sunsetquest

没想到linqpad可以做这些事情
jefissu


27

您可以使用LINQ ...它消除了检查字符串长度的需要。诚然,也许不是最有效的,但这很有趣。

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

要么

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

2
为什么这不是公认的答案?什么是最直接的,写自己的扩展方法,你需要保持/文件,或使用一些内置的喜欢。取
唐·钱德尔

9
@mmcrae Linq可能会更直接一些,但也要慢很多。我的基准测试表明,Linq约为400毫秒,Substring约为24毫秒,可进行一百万次迭代。
海因·安德烈·格伦尼斯塔

永远不要使用此解决方案。如以上两个注释中所述,即使现有字符串不大于最大长度,也总是存在内存分配。也很慢。
Kamarey

15

我是这样排成一排的

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

2
-1; 这根本不会添加公认的答案中没有的任何内容。
Mark Amery

2
@markamery是一个较短的替代方法,需要使用较少的代码即可编写和更新。不喜欢吗 不要使用它
SeanMC '18

快速,简单,快速。这就是我所需要的。谢谢!
彼得

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”。那是VB.NET API
Camilo Terevinto

9
@CamiloTerevinto-这是.NET Framework附带的API,可以从任何托管语言中调用。

1
VB DLL中包含很多好东西。为什么有这么多的C#开发人员反对呢?
Michael Z.

不幸的是,目前没有.NET Core支持。确实,Microsoft.VisualBasic.Strings.NET Core中的整个模块都非常空
Mark Amery

1
虽然我同意Joe的评论,但我仍然觉得用其他语言调用VB特有的东西并不正确。如果“ VB DLL”中包含很多好东西,为什么不将其放在某个共享的位置?谁知道微软明天会如何处理这些东西?停止支持或东西..
Kamarey


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该特殊字符显示为“。” 在cmd.exe中
Neal Ehardt,2015年

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

对于C#字符串,2016年仍然没有Truncate方法。但是-使用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

值得一提的是,截断字符串不仅意味着仅将字符串切割成指定的长度,还应注意不要拆分单词。

例如string:这是一个测试字符串。

我想在11点削减它。如果我们使用上面给出的任何方法,结果将是

这是一个te

这不是我们想要的东西

我使用的方法可能也不太理想,但可以处理大多数情况

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填充空间到最后或截断多余的空间。


您生成的字符串对象的数量是原来的两倍,并且如果value为null,则可能从PadRight调用中引发NullReferenceException,这应该是ArgumentNullException。
杰里米

1
@Jeremy我不明白“如果value为null,它可能会从PadRight调用中抛出NullReferenceException”;我没有提到“ //检查string.IsNullOrEmpty(value)并对其执行操作”。
斯里兰卡

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参数的后3个字符替换为省略号。

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


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

3
注意,在我的实现中,我选择不处理maximumLength小于0的情况,因为我认为我唯一要做的就是抛出ArgumentOutOfRangeExcpetion,它实际上是string.Substring()对我的作用。
jpierson

1

这是一个vb.net解决方案,标记if(尽管很丑)语句可以提高性能,因为当string已经小于maxlength时,我们不需要substring语句。通过扩展它可以方便地使用string。 ..

 <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”将同时检查null和空字符串大小写。使用以下命令进行测试:Truncate(“”,50)和Truncate(Nothing,50)
jrjensen

在VB中,您可以做Left(string,maxlength)
Michael

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

这是用于创建最大长度为260个字符的SharePoint URL。

我没有将length设为参数,因为它是常数260。我也没有将第一个子串的length设为参数,因为我希望它在特定点处断开。最后,第二个子字符串是源的长度-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中,可以使用新的Ranges功能...

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个字符的字符串,以防被截断。此外,三点实际上在表示上只是有意义的,我不会像OP给出的用例那样将其存储在数据库中。
MarioDS 2014年

0

截断字符串

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
为什么在公共方法名称前加下划线?
Michael Z.

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您可以通过无数个字符构造一个字符串,而不必通过数组。
Mark Amery

-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的问题。首先,它应该是成员函数(尽管您已将其编写为扩展方法)。其次,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();
        }

5
请注意,用+ =连接字符串是一项昂贵的操作,尤其是在逐字符重建字符时。.NET字符串是不可变的,这意味着在这种情况下,每次循环时都会创建一个新字符串。
Steve Guidi 2014年

@SteveGuidi字符串不是一成不变的,它们只是伪装成一成不变的。我希望字符串是真正的不可变基元,所以我可以使用string和string ?,但是可惜它们不是基元。
克里斯·马里西奇

您说的很昂贵,好像性能成本相当可观,我将其更改为使用stringBuilder,但我发现使用+ =可以更轻松地了解发生了什么,我只是希望OP能够轻松理解代码。
user3390116
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.