我有一个带有文本框的DetailsView
我希望输入的数据总是以大写的第一个字母保存。
例子:
"red" --> "Red"
"red house" --> " Red house"
我怎样才能实现性能最大化呢?
注意:
Based on the answers and the comments under the answers, many people think this is asking about capitalizing all words in the string. E.g. => Red House It isn't, but if that is what you seek, look for one of the answers that uses TextInfo's ToTitleCase method. (Note: Those answers are incorrect for the question actually asked.)
See TextInfo.ToTitleCase documentation for caveats (doesn't touch all-caps words - they are considered acronyms; may lowercase letters in middle of words that "shouldn't" be lowered, e.g., "McDonald" → "Mcdonald"; not guaranteed to handle all culture-specific subtleties re capitalization rules.)
注意:
第一个字母之后的字母是否必须小写,这个问题很模糊。公认的答案假定只有第一个字母需要修改。如果要强制字符串中除第一个字母外的所有字母都小写,请查找包含ToLower且不包含ToTitleCase的答案。
使用string.Create()并在方法中避免使用throw关键字(是的,您没有看错),我们可以进一步采用Marcell的答案。此外,我的方法处理任意长度的字符串(例如,几兆字节的文本)。
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位上运行的基准测试的数据。我添加了一个更长的字符串,以精确计算额外拷贝的成本。
Method |
Data |
Mean |
Error |
StdDev |
Median |
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 |
red house |
7.715 ns |
0.1741 ns |
0.4618 ns |
7.793 ns |
Marcell |
red house |
10.537 ns |
0.5002 ns |
1.4351 ns |
10.377 ns |
L33t |
red r(...)house [89] |
11.121 ns |
0.6774 ns |
1.9106 ns |
10.612 ns |
Marcell |
red r(...)house [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]);
});
}
}
}
如果你能让它更快,请告诉我!
我们可以这样做(c# 8.0, .NET 5):
input?.Length > 0 ? char.ToUpperInvariant(input[0]) + input[1..] : input
我相信这足够短,可以做内联。
如果input是一个空字符串,我们得到一个空字符串。如果input为null,则得到null。
否则,代码接受第一个字符输入[0],并使用char.ToUpperInvariant将其转换为大写。并连接其余的输入[1..]。
编译器会将范围访问转换为对Substring的调用,而且它可以利用我们已经获得了长度的事实。
与公认的答案相比,这样做的优点是不使用LINQ。其他一些答案将字符串转换为数组,只取第一个字符。这段代码也没有做到这一点。
如果你喜欢扩展方法,你可以这样做:
public static string FirstCharToUpper(this string input) =>
input?.Length > 0 ? char.ToUpperInvariant(input[0]) + input[1..] : input;
如果你更喜欢扔呢?好的,让它扔:
public static string FirstCharToUpper(this string input) =>
input switch
{
null => throw new ArgumentNullException(nameof(input)),
_ => input.Length > 0 ? char.ToUpperInvariant(input[0]) + input[1..] : input
};
这里是大致相同的代码(因为我们正在创建一个扩展方法,所以我们可以更详细一点):
public static string FirstCharToUpperEquivalent(this string input)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}
var length = input.Length;
if (length == 0)
{
return input;
}
string firstCharacter = char.ToUpperInvariant(input[0]).ToString();
return string.Concat(firstCharacter, input.Substring(1, length - 1));
}
我做了1000轮155个单词的基准测试(所以它们被调用了155000次),结果是:
Benchmarking type Tests
TestAccepted 00:00:00.0465979
TestProposalNoThrow 00:00:00.0092839
TestProposalDoThrow 00:00:00.0092938
TestProposalEquival 00:00:00.0091463
我在Windows 10、英特尔酷睿i3上运行它,使用Jon Skeet用c#编写的简单微基准测试代码。