Summary#
Write the string code you would write in C#. s.Contains(v, StringComparison.OrdinalIgnoreCase),
string.Compare(a, b, StringComparison.Ordinal), s.ToLower(new CultureInfo("tr-TR")),
s.Split(',', StringSplitOptions.RemoveEmptyEntries) and string.Format("{0} items", n) all answer what C# answers.
They give the same answer in a function, in the browser and inside a query.
Signature#
bool s.Contains(string value, StringComparison mode)
int string.Compare(string a, string b, StringComparison mode)
string s.ToLower(CultureInfo culture)
List<string> s.Split(char separator, StringSplitOptions options)
string s.Normalize(NormalizationForm form)
string string.Format(string format, object arg0, …)
int a.CompareTo(string b) · string.CompareOrdinal(string a, string b)
int s.IndexOfAny(char[] anyOf) · s.LastIndexOfAny(char[] anyOf)
string s.ReplaceLineEndings() · s.ReplaceLineEndings(string replacement)
bool s.IsNormalized() · s.IsNormalized(NormalizationForm form)
string new string(char[] chars) · new string(char[] chars, int start, int length)
string string.Intern(string s) · s.Clone()
int s.GetHashCode() · TypeCode s.GetTypeCode()Description#
How do the comparison modes differ?#
StringComparison.Ordinal compares UTF-16 code units. OrdinalIgnoreCase compares them after .NET's invariant
upper-casing. The two InvariantCulture modes compare linguistically. A CurrentCulture mode compares in the app's
current culture — the viewer's, else app.DefaultCulture — and is the invariant culture in an app that declares none;
it is never the host machine's (Culture formatting — ToString(format, culture)). So is a culture-less ToUpper()/ToLower().
Inside a query, a mode you write out as a literal picks the SQL. Ordinal becomes the plain comparison and
OrdinalIgnoreCase an upper-cased one. A linguistic mode has no SQL form, because the database compares under its
own collation and not .NET's, so a query that uses one is refused by name. Compare after .ToList() instead.
Cultures are their names#
A culture is held as its name. new CultureInfo("en-US") and CultureInfo.GetCultureInfo("en-US") mean exactly
that name, and CultureInfo.InvariantCulture means the culture-free one. Any member that takes a culture takes any of
these. A name .NET does not know is refused when you compile, where C# would throw CultureNotFoundException when it
runs.
Casing under a culture follows .NET exactly, and it matters for the Turkish i.
"İ".ToLower(c) | "ı".ToUpper(c) | "i".ToUpper(c) | |
|---|---|---|---|
CultureInfo.InvariantCulture | "İ" | "ı" | "I" |
new CultureInfo("en-US") | "i" | "I" | "I" |
new CultureInfo("tr-TR") | "i" | "I" | "İ" |
How do splitting, trimming and normalising work?#
Split takes a char, a string, or an array of either, and also a count and StringSplitOptions. The options are
RemoveEmptyEntries, TrimEntries or both, combined with |. Trim, TrimStart and TrimEnd take a character
set; an empty set trims white space. Normalize and IsNormalized take a NormalizationForm; with none they
mean FormC, so "cafe\u0301".Normalize() is the one-character "café" and "cafe\u0301".IsNormalized() is
false. s.ReplaceLineEndings("\n") turns every line ending — \r\n, \r, \n and the Unicode ones — into the
text you give; with no argument it uses "\n", on the server and in the browser alike.
Where in the string is it?#
IndexOf, LastIndexOf, IndexOfAny and LastIndexOfAny count in UTF-16 units and follow .NET's argument rules.
In particular, LastIndexOf(value, startIndex) accepts a start of exactly Length, and an empty value is found just
past the start. Inside a query the same numbers come back, including for text that holds emoji. A cut that would
split an emoji in half raises an error rather than storing half a character.
How do I write values into text?#
string.Format("{0} of {1}", done, total) and string.Concat(name, 5) take values of any type, as C# does.
string.Format reads its format when you compile and turns it into an interpolation, so the format must be written
out in the source. A format held in a variable is refused, and the refusal shows the interpolation to write instead.
What about the rest of string?#
a.CompareTo(b) and string.Compare(a, b) compare under the current culture, as C#'s do, and string.CompareOrdinal(a, b) by code unit —
so string.CompareOrdinal("a", "B") is positive, because 'a' comes after 'B' in UTF-16. new string(chars) and
new string(chars, start, length) build a string from a char[]; new string(c, n) repeats one character
(Text.TrimStart, Text.TrimEnd, Text.PadStart, Text.PadEnd, PadLeft, PadRight). s.GetHashCode() is equal for equal strings within a run (as in C#, never store it),
s.GetTypeCode() is TypeCode.String, and s.Clone() and string.Intern(s) answer the same string — a string
is immutable, so there is nothing to copy or pool.
Examples#
entity Customer {
[Required, MaxLength(120)] string Name;
security { allow read, create when IsAuthenticated; }
}
List<Customer> Matching(string term) {
return Customer.Where(c => c.Name.Contains(term, StringComparison.OrdinalIgnoreCase)).ToList();
}string ForTurkishReaders(string s) {
return s.ToUpper(new CultureInfo("tr-TR"));
}List<string> Fields(string row) {
return row.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
}bool HasNote(string? note) {
return !string.IsNullOrWhiteSpace(note);
}string Summary(int done, decimal total) {
return string.Format("{0} done, {1} left", done, total);
}[Test]
void String_members_answer_what_csharp_answers() {
Assert.Equal("a|b|c", "a\r\nb\nc".ReplaceLineEndings("|"));
Assert.Equal("café", "cafe\u0301".Normalize());
Assert.False("cafe\u0301".IsNormalized());
Assert.Equal(1, "a,b;c".IndexOfAny([',', ';']));
Assert.Equal(3, "a,b;c".LastIndexOfAny([',', ';']));
Assert.Equal("a\nb", "a\r\nb".ReplaceLineEndings());
Assert.True("a".CompareTo("B") < 0); // linguistic: a before B
Assert.True(string.CompareOrdinal("a", "B") > 0); // by code unit: 'a' (97) after 'B' (66)
char[] letters = ['o', 's', 'y'];
Assert.Equal("osy", new string(letters));
Assert.Equal("sy", new string(letters, 1, 2));
Assert.Equal("abc", "abc".Clone());
Assert.Equal("x", string.Intern("x"));
Assert.Equal("abc".GetHashCode(), ("ab" + "c").GetHashCode());
Assert.Equal(TypeCode.String, "abc".GetTypeCode());
}See also#
- char — one character, and why
Split(',')andSplit(",")differ - Text.Length, Text.IsEmpty, Text.IsBlank, Text.Contains, Text.StartsWith, Text.EndsWith — the everyday questions: contains, starts with, index of
- Culture formatting — ToString(format, culture) — numbers and dates written for a reader's culture