使字符串的第一个字母大写(最大程度地提高性能)

620

我有一个 DetailsView ,其中包含一个 TextBox,我希望 输入数据 总是以 大写字母首字母 保存。

示例:

"red" --> "Red"
"red house" --> " Red house"
如何实现“最大化性能”?(原文中的“maximizing performance”已经使用强调标签进行了突出)注意:根据答案和答案下面的评论,许多人认为这是在要求将字符串中的所有单词都大写。例如:=>红房子。但实际上不是这样,如果你正在寻找这种效果,请查看其中一个答案,该答案使用TextInfoToTitleCase方法。(注意:实际上对于所提出的问题,这些答案是错误的。) 有关警告信息,请参见TextInfo.ToTitleCase文档。 注意:由于被视为首字母缩略语,因此它们不会涉及到全大写单词;可能会将“不应”小写的单词中间的字母小写,例如“麦当劳”→“mcdonald”;不能保证处理所有与特定文化相关的大小写规则的微妙之处。
注意:该问题不清楚是否应强制第一个字母之后的字母转换为小写。 接受的答案假设只有第一个字母应更改。 如果想要强制除第一个字母以外的所有字母小写,请查找包含ToLower并且不包含ToTitleCase的答案。

10
@Bobby:这不是重复问题:OP要求将字符串的第一个字母大写,而链接中的问题是将每个单词的第一个字母大写。 - GvS
2
@GvS:第一个答案非常详细,第一个代码块正是他所需要的。此外,每个单词都大写和只有第一个单词大写之间只有一个循环差异。 - Bobby
2
但是你说过,并且我引用一下,“让每个单词的第一个字母大写”。因此,为什么“red house”变成了“Red house”?为什么“house”的“h”不是大写字母? - Guillermo Gutiérrez
不要忘记,假设您正在使用计算机,您可以执行以下操作:https://dev59.com/MnM_5IYBdhLWcg3w1G-N#1206029 - Fattie
@Fattie - 有用的链接,但是这个问题不是关于每个单词都大写的问题——它是关于将字符串的第一个字母改为大写的问题。 - ToolmakerSteve
44个回答

813

不同C#版本的解决方案

C# 8与至少.NET Core 3.0或.NET Standard 2.1

public static class StringExtensions
{
    public static string FirstCharToUpper(this string input) =>
        input switch
        {
            null => throw new ArgumentNullException(nameof(input)),
            "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)),
            _ => string.Concat(input[0].ToString().ToUpper(), input.AsSpan(1))
        };
}

自 .NET Core 3.0 / .NET Standard 2.1 开始,String.Concat() 支持 ReadonlySpan<char>。如果我们使用 .AsSpan(1) 而不是 .Substring(1),可以节省一个内存分配。

C# 8

public static class StringExtensions
{
    public static string FirstCharToUpper(this string input) =>
        input switch
        {
            null => throw new ArgumentNullException(nameof(input)),
            "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)),
            _ => input[0].ToString().ToUpper() + input.Substring(1)
        };
}

C# 7

public static class StringExtensions
{
    public static string FirstCharToUpper(this string input)
    {
        switch (input)
        {
            case null: throw new ArgumentNullException(nameof(input));
            case "": throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input));
            default: return input[0].ToString().ToUpper() + input.Substring(1);
        }
    }
}

非常老的答案

public static string FirstCharToUpper(string input)
{
    if (String.IsNullOrEmpty(input))
        throw new ArgumentException("ARGH!");
    return input.First().ToString().ToUpper() + String.Join("", input.Skip(1));
}

这个版本更短。如果想要更快的解决方案,请查看Diego的回答

public static string FirstCharToUpper(string input)
{
    if (String.IsNullOrEmpty(input))
        throw new ArgumentException("ARGH!");
    return input.First().ToString().ToUpper() + input.Substring(1);
}

可能最快的解决方法是Darren的(甚至有一个基准测试),尽管我会改变它的string.IsNullOrEmpty(s)验证以抛出异常,因为原始要求期望首字母存在,以便可以将其大写。请注意,此代码适用于通用字符串,而不是特定于来自Textbox的有效值。


2
因为 String.Join 的第一个参数是用于连接第二个参数中给定的字符串的分隔符。 - Dialecticus
36
我很喜欢你的回答,不过 var arr = input.ToCharArray(); arr[0] = Char.ToUpperInvariant(arr[0]); return new String(arr); 可能会更快,因为你创建了更少的不可变对象(特别是跳过了 String.Join)。当然,这取决于字符串的长度。 - flindeberg
3
很棒 - 使用 Linq 让这段代码的作用非常清晰。 - Daniel James Bryars
12
从技术上讲,这应该返回“Argh!”以遵守首字母大写规则。;) - user153923
2
由于在空字符串或空值中大写不存在的第一个字母就像被怀孕的海豚打耳光一样,所以全大写的“ARGH!”是正确的拼写。@jp2code http://www.urbandictionary.com/define.php?term=ARGH&defid=67839 - Carlos Muñoz
显示剩余28条评论

397
public string FirstLetterToUpper(string str)
{
    if (str == null)
        return null;

    if (str.Length > 1)
        return char.ToUpper(str[0]) + str.Substring(1);

    return str.ToUpper();
}

这也可以写成

public string ToTitleCase(string str)
{
    var firstword = System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.Split(' ')[0].ToLower());
    str = str.Replace(str.Split(' ')[0],firstword);
    return str;
}

它会提取第一个单词并将其转换为标题大小写,然后在输入字符串中替换它。


但是这会将每个单词的第一个字母都转换为大写,而不仅仅是字符串的第一个字符。 - GvS
26
他问“red house” => “红色房子”。使用ToTitleCase将会得到“红色房子”。 - GvS
1
@GvS,是的,这就是为什么我说那是我的旧答案,并将每个首字母大写。 - Diego
1
不确定,但 char + string 会导致装箱。以防万一需要最大的性能要求。 - nawfal
返回 char.ToUpper(str[0]) + str.Substring(1); 在第二部分上必须添加 to lower,否则在输入全部大写时它将无法工作。 - ArnaldoRivera
显示剩余4条评论

222

正确的方法是使用Culture:

System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(word.ToLower())

注意:此代码将使字符串中的每个单词首字母大写,例如 "red house" --> "Red House"。该解决方案还会将单词内的大写字母转成小写,例如 "old McDonald" --> "Old Mcdonald"。


28
六年后,请求您更加仔细地阅读现有答案及其评论。如果您确信自己有更好的解决方案,则请展示您认为更优越的情况,并具体说明与现有答案的区别。1)Equiso已经在他的回答的后半部分涵盖了这个选项。2)对于许多情况,“ToLower”是一个错误,因为它会抹掉单词中间的大写字母,例如“麦当劳”。3)问题是关于只更改字符串的第一个单词,而不是关于TitleCase。 - ToolmakerSteve
2
好的文化不会有任何伤害。其他观众可能没有“仅限首字母”的要求。 - JB.
一个同样正确的答案可能是 word.ToUpper() - 它将第一个字母改为大写,我们不用担心其余部分。 - Peter Hedberg
“Culture” 是字面意思吗? - Peter Mortensen

92

我从 C# Uppercase First Letter - Dot Net Perls 中采用了最快的方法,并将其转换为一个扩展方法:

    /// <summary>
    /// Returns the input string with the first character converted to uppercase, or mutates any nulls passed into string.Empty
    /// </summary>
    public static string FirstLetterToUpperCaseOrConvertNullToEmptyString(this string s)
    {
        if (string.IsNullOrEmpty(s))
            return string.Empty;

        char[] a = s.ToCharArray();
        a[0] = char.ToUpper(a[0]);
        return new string(a);
    }

注意:使用 ToCharArray 比替代方案 char.ToUpper(s[0]) + s.Substring(1) 更快的原因是,只分配了一个字符串,而 Substring 方法会为子字符串分配一个字符串,然后再分配第二个字符串来组成最终结果。


这是该方法与来自CarlosMuñoz已接受的答案的初始测试的结合:

    /// <summary>
    /// Returns the input string with the first character converted to uppercase
    /// </summary>
    public static string FirstLetterToUpperCase(this string s)
    {
        if (string.IsNullOrEmpty(s))
            throw new ArgumentException("There is no first letter");

        char[] a = s.ToCharArray();
        a[0] = char.ToUpper(a[0]);
        return new string(a);
    }

哇,感谢您找到性能指标,展示出卓越的性能解决方案! - ToolmakerSteve
2
@CarlosMuñoz - 在meta中已经讨论过是否“改进”他人的答案。共识是“如果你能够改进一个答案,那么就这样做——没有人‘拥有’一个答案,甚至原始作者也不行——目标是获得最好的可能答案”。当然,您可以自由编辑或回滚编辑。在这种情况下,常规礼貌会让原始作者的版本成为最终结果,而我将只评论。通常我还会在更改时添加一条评论;如果我没有这样做,我很抱歉。 - ToolmakerSteve
1
Darren,我将编辑您的答案以添加替代代码,展示@CarlosMuñoz的初始测试中您的解决方案的样子。我相信您的贡献是找到了更高效的解决方案,您不会介意这个补充。如果您不喜欢它,请根据我的编辑自行处理。 - ToolmakerSteve
1
@ToolmakerSteve 同意最好不要将 null 变成空字符串,但对于我的用例来说很方便,所以我会只是重命名原始方法。 - Darren
2
我对所有答案进行了基准测试,这个答案获胜了。同时改用char.ToUpperInvariant的速度甚至快20%。 - Alex from Jitbit
显示剩余3条评论

53

您可以使用"ToTitleCase方法":

string s = new CultureInfo("en-US").TextInfo.ToTitleCase("red house");
//result : Red House

这个扩展方法解决了所有标题大小写问题。

使用起来很容易:

string str = "red house";
str.ToTitleCase();
//result : Red house

string str = "red house";
str.ToTitleCase(TitleCase.All);
//result : Red House

扩展方法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

namespace Test
{
    public static class StringHelper
    {
        private static CultureInfo ci = new CultureInfo("en-US");
        //Convert all first latter
        public static string ToTitleCase(this string str)
        {
            str = str.ToLower();
            var strArray = str.Split(' ');
            if (strArray.Length > 1)
            {
                strArray[0] = ci.TextInfo.ToTitleCase(strArray[0]);
                return string.Join(" ", strArray);
            }
            return ci.TextInfo.ToTitleCase(str);
        }

        public static string ToTitleCase(this string str, TitleCase tcase)
        {
            str = str.ToLower();
            switch (tcase)
            {
                case TitleCase.First:
                    var strArray = str.Split(' ');
                    if (strArray.Length > 1)
                    {
                        strArray[0] = ci.TextInfo.ToTitleCase(strArray[0]);
                        return string.Join(" ", strArray);
                    }
                    break;
                case TitleCase.All:
                    return ci.TextInfo.ToTitleCase(str);
                default:
                    break;
            }
            return ci.TextInfo.ToTitleCase(str);
        }
    }

    public enum TitleCase
    {
        First,
        All
    }
}

2
你的解决方案存在问题,即“red house”会被转换为“Red House”,而不是问题中要求的“Red house”。 - Vadim
5
这段代码是可行的,但以下代码更易于阅读并且性能更高 char.ToUpper(text[0]) + ((text.Length > 1) ? text.Substring(1).ToLower() : string.Empty); 您可以在此处阅读更多内容:http://vkreynin.wordpress.com/2013/10/09/capitalize-only-first-character-c/ - Vadim
2
我不喜欢这个解决方案,因为它将两种非常不同的情况合并成一个冗长的方法。我也看不到概念上的好处。而且仅大写第一个字母的实现是荒谬的。如果你想要大写第一个字母,显然的实现方式是只大写(ToUpper)第一个字母。与此相反,我会有两个单独的方法。在Equiso的答案中使用FirstLetterToUpper(或在Guillernet的新答案中),并在这里使用ToTitleCase,但不带第二个参数。然后就不需要enum TitleCase了。 - ToolmakerSteve

39

对于第一个字母,进行错误检查:

public string CapitalizeFirstLetter(string s)
{
    if (String.IsNullOrEmpty(s))
        return s;
    if (s.Length == 1)
        return s.ToUpper();
    return s.Remove(1).ToUpper() + s.Substring(1);
}

这里也有一个方便的扩展程序

public static string CapitalizeFirstLetter(this string s)
{
    if (String.IsNullOrEmpty(s))
        return s;
    if (s.Length == 1)
        return s.ToUpper();
    return s.Remove(1).ToUpper() + s.Substring(1);
}

清晰的方法。谢谢! - Philippe

16

使用string.Create()方法,并避免在我们的方法中使用throw关键字(是的,您没看错),我们可以将Marcell's answer进一步升级。此外,我的方法可以处理任意长度的字符串(例如,数兆字节的文本)。

public static string L33t(this string s)
{
    static void ThrowError() => throw new ArgumentException("There is no first letter");

    if (string.IsNullOrEmpty(s))
        ThrowError();                      // No "throw" keyword to avoid costly IL

    return string.Create(s.Length, s, (chars, state) =>
    {
        state.AsSpan().CopyTo(chars);      // No slicing to save some CPU cycles
        chars[0] = char.ToUpper(chars[0]);
    });
}

性能

这里是在.NET Core 3.1.7,64位上运行基准测试的数据。我添加了一个更长的字符串以确定额外复制的成本。

方法 数据 平均值 误差 标准偏差 中位数
L33t red 8.545 ns 0.4612 ns 1.3308 ns 8.075 ns
Marcell red 9.153 ns 0.3377 ns 0.9471 ns 8.946 ns
L33t 红色的房子 7.715 ns 0.1741 ns 0.4618 ns 7.793 ns
Marcell 红色的房子 10.537 ns 0.5002 ns 1.4351 ns 10.377 ns
L33t 红色的房子 [89] 11.121 ns 0.6774 ns 1.9106 ns 10.612 ns
Marcell 红色的房子 [89] 16.739 ns 0.4468 ns 1.3033 ns 16.853 ns

完整测试代码

using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

namespace CorePerformanceTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var summary = BenchmarkRunner.Run<StringUpperTest>();
        }
    }

    public class StringUpperTest
    {
        [Params("red", "red house", "red red red red red red red red red red red red red red red red red red red red red house")]
        public string Data;

        [Benchmark]
        public string Marcell() => Data.Marcell();

        [Benchmark]
        public string L33t() => Data.L33t();
    }

    internal static class StringExtensions
    {
        public static string Marcell(this string s)
        {
            if (string.IsNullOrEmpty(s))
                throw new ArgumentException("There is no first letter");

            Span<char> a = stackalloc char[s.Length];
            s.AsSpan(1).CopyTo(a.Slice(1));
            a[0] = char.ToUpper(s[0]);
            return new string(a);
        }

        public static string L33t(this string s)
        {
            static void ThrowError() => throw new ArgumentException("There is no first letter");

            if (string.IsNullOrEmpty(s))
                ThrowError(); // IMPORTANT: Do not "throw" here!

            return string.Create(s.Length, s, (chars, state) =>
            {
                state.AsSpan().CopyTo(chars);
                chars[0] = char.ToUpper(chars[0]);
            });
        }
    }
}

如果您能加快速度,请告诉我!


为什么在string.IsNullOrEmpty之后不能使用throw关键字(重要:此处不要"throw"!)? - Casper
1
可以使用throw关键字,但它的存在会阻止某些编译器优化,例如内联。你可以在有和没有它的情况下运行示例,你会发现它确实会影响性能。 - l33t

14

由于这个问题涉及到最大化性能,我采用了达伦的版本使用Span,这可以减少垃圾并提高约10%的速度。

/// <summary>
/// Returns the input string with the first character converted to uppercase
/// </summary>
public static string ToUpperFirst(this string s)
{
    if (string.IsNullOrEmpty(s))
        throw new ArgumentException("There is no first letter");

    Span<char> a = stackalloc char[s.Length];
    s.AsSpan(1).CopyTo(a.Slice(1));
    a[0] = char.ToUpper(s[0]);
    return new string(a);
}

性能

方法 数据 平均值 误差 标准偏差
Carlos red 107.29 ns 2.2401 ns 3.9234 ns
Darren red 30.93 ns 0.9228 ns 0.8632 ns
Marcell red 26.99 ns 0.3902 ns 0.3459 ns
Carlos red house 106.78 ns 1.9713 ns 1.8439 ns
Darren red house 32.49 ns 0.4253 ns 0.3978 ns
Marcell red house 27.37 ns 0.3888 ns 0.3637 ns

完整测试代码

using System;
using System.Linq;

using BenchmarkDotNet.Attributes;

namespace CorePerformanceTest
{
    public class StringUpperTest
    {
        [Params("red", "red house")]
        public string Data;

        [Benchmark]
        public string Carlos() => Data.Carlos();

        [Benchmark]
        public string Darren() => Data.Darren();

        [Benchmark]
        public string Marcell() => Data.Marcell();
    }

    internal static class StringExtensions
    {
        public static string Carlos(this string input) =>
            input switch
            {
                null => throw new ArgumentNullException(nameof(input)),
                "" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)),
                _ => input.First().ToString().ToUpper() + input.Substring(1)
            };

        public static string Darren(this string s)
        {
            if (string.IsNullOrEmpty(s))
                throw new ArgumentException("There is no first letter");

            char[] a = s.ToCharArray();
            a[0] = char.ToUpper(a[0]);
            return new string(a);
        }

        public static string Marcell(this string s)
        {
            if (string.IsNullOrEmpty(s))
                throw new ArgumentException("There is no first letter");

            Span<char> a = stackalloc char[s.Length];
            s.AsSpan(1).CopyTo(a.Slice(1));
            a[0] = char.ToUpper(s[0]);
            return new string(a);
        }
    }

}

4
你应该防范过长的字符串。如果你有超过大约1MB的字符数,stackalloc会破坏栈。 - l33t
请查看我的改进答案,使用string.Create() - l33t

13
public static string ToInvarianTitleCase(this string self)
{
    if (string.IsNullOrWhiteSpace(self))
    {
        return self;
    }

    return CultureInfo.InvariantCulture.TextInfo.ToTitleCase(self);
}

9

最快的方法:

private string Capitalize(string s){
    if (string.IsNullOrEmpty(s))
    {
        return string.Empty;
    }
    char[] a = s.ToCharArray();
    a[0] = char.ToUpper(a[0]);
    return new string(a);
}

测试结果(输入字符串为1,0000,000个符号)如下:

测试结果


注意:本翻译仅供参考,具体语境需要根据实际情况进行调整。

4
建议在参数s为空时返回该参数。 - MatrixRonny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接