Osy#betaRun it on Osyrin: osyrin.com

Reference / Types

Comparing, casing and splitting text the C# way

s.Contains(v, StringComparison.OrdinalIgnoreCase) · s.ToLower(new CultureInfo("tr-TR")) · s.Split(',', StringSplitOptions.RemoveEmptyEntries) · string.Compare(a, b, StringComparison.Ordinal)

The members of C#'s `string` that take a `StringComparison`, a `CultureInfo`, a `StringSplitOptions` or a `NormalizationForm` work as in C#, with C#'s answers. They work in a function, in the browser and, where the database can say the same thing, inside a query.

stable6 examples compiled by CItypestextauthoring

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#

Related

char

A single character, written in single quotes. It is what you get from `s[0]` and from iterating a string, and it is the…

string literals — ordinary, verbatim and raw

Three ways to write a string, all of them C#'s. The ordinary form processes escapes. The verbatim form (`@"…"`)…

Text.Length, Text.IsEmpty, Text.IsBlank, Text.Contains, Text.StartsWith, Text.EndsWith

Ask a string a question without changing it: its length, whether it is empty or blank, and whether it contains, starts…

Culture formatting — ToString(format, culture)

Format numbers, currency, percentages and dates for a declared culture — `total.ToString("C", "sv-SE")` → `1 234,56…