String comparison performed without an explicit StringComparison

ID

vbnet.correctness.stringcomparison

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

String Handling

Language

VB.NET

Description

Reports string comparisons that do not state which comparison rules to apply: the single-argument StartsWith and EndsWith overloads, the two-argument String.Compare(left, right) overload, and Equals called with a string literal and no StringComparison. Each of these has an overload that takes a StringComparison, and that overload is what the rule asks for. Calls that already pass a StringComparison are accepted, and Contains is not reported because it is ordinal by definition. Two forms are excluded because no StringComparison overload exists for them: a character-literal argument such as StartsWith("-"c), and Equals("") - an empty-string test, whose proper fix is String.IsNullOrEmpty rather than a comparison mode.

Rationale

StartsWith, EndsWith and String.Compare default to a culture-sensitive comparison driven by the current thread culture, so their result depends on where the process happens to be running. Culture-sensitive rules are not simple character matching: under many cultures certain characters are ignored for comparison purposes, and comparison order differs between cultures - in Swedish z sorts before the last letters of the alphabet, in Hungarian dzs collates as one unit, and Turkish treats dotted and dotless i as separate letters, so a prefix test that passes on a developer machine can fail on a customer’s. When the strings being compared are not human-readable text at all - a file extension, a URL scheme, a registry key, a role name, an account prefix - culture is the wrong rule in every locale, and the resulting mismatch shows up as a permission check that silently returns the wrong answer or a lookup that finds nothing. Equals is already ordinal, but with no StringComparison argument the reader cannot tell whether ordinal was chosen or merely inherited, and a later edit to a culture-sensitive method is invisible in review.

The following code illustrates the pattern detected by this rule:

Public Function IsAdministrator(ByVal roleName As String) As Boolean
    ' FLAGGED: String comparison performed without an explicit StringComparison
    Return roleName.Equals("administrator")
End Function

Remediation

Pass an explicit StringComparison. For identifiers, protocol tokens, file paths, keys and any other value not shown to a user, use StringComparison.Ordinal (or StringComparison.OrdinalIgnoreCase when case should be ignored) - it is both correct and the fastest option. Reserve StringComparison.CurrentCulture for text that is genuinely being compared or ordered on behalf of the user, and use StringComparison.InvariantCulture only for a stable, culture-independent linguistic ordering.

' Before: the current culture decides the result
Return roleName.Equals("administrator")
Return emailAddress.EndsWith("@example.com")
Return String.Compare(left, right)

' After: the rules are stated at the call site
Return roleName.Equals("administrator", StringComparison.OrdinalIgnoreCase)
Return emailAddress.EndsWith("@example.com", StringComparison.OrdinalIgnoreCase)
Return String.Compare(left, right, StringComparison.Ordinal)

Configuration

This detector does not need any configuration.