- Table of Contents
- .NET Book Recommendations
- What Is .NET?
- The Microsoft .NET Framework
- The Common Language Runtime (CLR), the Common Type System (CTS), and the Common Language Specification (CLS)
- .NET Framework Class Library
- Visual Studio .NET
- .NET Enterprise Servers and .NET My Services
- .NET Compliant Languages
- C#
- Visual Basic .NET (VB .NET)
- ASP.NET
- XML Web Services
- ADO.NET
- XML.NET
- Windows Forms
- Why .NET?
- Displaying Errors with the Error Provider
- COM Interoperability
- Comparing Java and .NET
- Calling Unmanaged Code
- .NET Application Security
- Code Access Security
- .NET Standards Support
- Numeric Types in the .NET Framework
- Working with Strings
- Formatting Strings
- Trimming Character Strings
- Comparing Strings in .NET 2.0
- Arrays and Collections
- Arrays as Class Members
- Sorting a Multi-Dimensional Array
- File I/O (System.IO)
- Working with File Names
- Using the File System
- Working with Files and Directories
- Monitoring the File System
- Working with Streams
- Working with Text Encodings
- Working with Date and Time
- Extending the DateTime Class
- Fun with Dates
- Exceptions
- Delegates
- Events
- Asynchronous Programming
- Asynchronous File I/O
- Timers
- Random Numbers
- Serialization
- MultiThreading (System.Threading)
- Multi-Threading Overview
- The Managed Thread Pool
- Managed Threading
- Thread Synchronization
- Synchronizing Data Access
- Trace Debugging
- Tracing in .NET 2.0
- ASP.NET Trace
- Validating User Input in ASP.NET Web Pages
- Event Logging
- Monitoring Application Performance
- Accessing the Registry
- Accessing Environment Information
- Environment Variables in .NET 2.0
- Managing Windows Forms Applications
- Working with Email
- Working with Graphics
- Animating a Background
- Working with Images
- Drawing Cycloid Curves
- Simulating the Spirograph
- Building International Web Applications
- .NET Compact Framework
- Mobile Web Development with ASP.NET
- Speech Technologies
- Microsoft MapPoint Web Service
- Working with Typed DataSets
- Using Relationships in DataSets
- DataColumn Expressions
- Playing Simple Sounds
- Playing Sounds with .NET 2.0
- Returning an Image in a Web Page
- RSS
- Best Practices Project Structure
- Best Practices Application Blocks
- The Data Access Application Block
- The Exception Management Application Block
- Best Practices — Performance
- Best Practices — Performance and Scalability
- Best Practices - Testing
- Reading the Tea Leaves, 2005
- Predictions: A Look Back at 2005, and a Look Ahead to 2006
- .NET Downloads
- Application Deployment Overview
- Application Deployment — Versioning
- Application Deployment — Version Policy
- Application Deployment — Packaging and Distribution
- .NET Remoting Overview
- A Remoting Demonstration
- Remoting Configuration
- Remoting: Lifetimes and Leases
- Remoting: Other Issues
- Attributes
- Writing Custom Attributes
- Accessing Attributes in Code
- Reflection
- Class Design: Inheritance, Interface, or Composition?
- The TriTryst Game
- Console Applications in .NET 2.0
- New File I/O Methods in .NET 2.0
- Building Projects with MSBuild
- Unmanaged Callbacks in .NET 2.0
- Timer Troubles
- Non-Rectangular Windows Forms
- Windows Forms Transparency
- 10 Things I Hate About Visual Basic
- 10 Things I Hate About C#
- Background Processing with Idle Time
- Scaling Windows Forms
- Reading and Writing Binary Data
- New Memory Management Functions in .NET 2.0
- Compatibility Between .NET 1.1 and .NET 2.0
- Managed Debugging Assistants in .NET 2.0
- XDir: A Program for Viewing Directory Sizes
- The Microsoft.VisualBasic Namespace
- Operator Overloading
- Working with GPS Data
- Hidden Visual Studio Tools
- .NET 3.0
- The .NET 2.0 Stopwatch Class
- Nullable Types
- Drawing Rotated Text
- Unsafe Code
- Other .NET Languages
- Compiler Directives
- Safe Handles
- Predictions, 2007 Edition
- New Features in C# 3.0
- Generics
- Network Client Programming
- On the Misuse of Exceptions
- Maximum Object Size in .NET
- More on Maximum Object Sizes
- Keyed Collection Memory Limitations
- Matching String Endings
- Allocating Small Data Structures
- Grumbling About Limitations
- Some Thoughts on the Nature of What We Do
- Working with Predicates in Collections
- Working with DataReaders
- Outputting XML with XmlWriter
- Writing XML Data
- Working with Compression
- Another Look at Compressed Streams
- Compressing a Very Large File
- Canonical URIs
- Constructing URIs
- Using OneWayAttribute for Remote Calls
- Selecting a Garbage Collector
- Linked List
- Linked List Application - The MRU List
- Auto-implemented Properties in C#
- The HashSet Collection
- Looking Ahead: 2018
- An Experiment in Optimization
- A Larger Integer
- Extension Methods
- Language Integrated Query (LINQ)
- Variable Length Parameter Lists
- The ReaderWriterLockSlim Synchronization Primitive
- Sorting a Text File
- Sorting a Large Text File
- Using ListView with Large Data Sets
- LINQ One-Liners
- Regular Expression Optimization
- Random File I/O
- Computing the Size of a Structure
- More on Computing Structure Sizes
- UnmanagedMemoryStream
- Dynamically Loading Code
- Building a String Table
- Delegates Versus Function Pointers
- Visual Studio Editor Features
- A Simple Profile Timer
- New Features in C# 4.0
- IEnumerator or IList?
- New Features in .NET 4.0
- Set Operations with IEnumerable and HashSet
- Using File Locks
- Extending Object Functionality
- Clearing a HashSet
- When Hash Codes Matter
- Parsing Command Line Options
- Creating a Single-Instance Program
- Asynchronous Windows Forms Events
- The BackgroundWorker Component
- Fixing a Dumb Mistake
- Thinking About Multi-Threaded Programs
- JavaScript Object Notation
- Useful .NET-related Sites
- Markov Models
- Building an Order 0 Markov Model
- Higher Order Markov Models
- Webmaster's Guide to robots.txt
- An Overview of the Parallel Extensions to .NET
- Parallel Extensions Synchronization Objects
- Thread Safe Collections
- A Bug and a Conundrum
- Another Bug and an Answer
- Task Parallel Library
- Good and Bad Ideas in C#
- Parallel LINQ
- Copying Large Files
- Replacing File.Copy
- Learning from Our Mistakes
- Symbolic Links
- There Is No Easy Fix
- Tracking Hurricanes
- Examining Hurricane Data
- Searching for Multiple Strings
- Simple JSON Processing
- Aho-Corasick String Searching New
- Writing a Web Crawler New
- Web Crawler Politeness New
- Source Control Management New
- Informit Reference Library
Comparing Strings in .NET 2.0
Last updated May 19, 2006.
Sometimes the seemingly simplest things turn out to be the most difficult. For example, one would think that comparing strings in a program would be simple. It turns out that things can get ugly very quickly, and prior to .NET 2.0 there wasn’t a foolproof way to eliminate the problem.
The designers of the .NET Framework were very careful to include support for different cultures, and to implement the string comparison rules for each culture. This works very well when comparing culture-sensitive strings. But very often in our programs we want to compare culture-invariant strings: strings that must compare identically regardless of the culture. Handled incorrectly, such comparisons can produce undesirable results.
The "Turkish I" problem
For example, "everybody knows" that if you compare the strings "file" and "FILE" without regard to case, they’ll compare equal, right? That is:
if (string.Compare("file", "FILE", true) == 0)
{
Console.WriteLine("’file’ is equal to ’FILE’");
}
That will work every time, right? Not quite. As it turns out, in Turkish the "capital I" is Ý (\u0130)--a "capital i with a dot." This is the capital version of the character "i". In Turkish there also is a lowercase "i without a dot", (\u0131), which capitalizes to "I".
The above is known as the "Turkish-I problem," and is perhaps the best-known example of how culture-sensitive comparisons on culture-insensitive strings can produce incorrect results. The rules for capitalizing i or lowercasing I differ among cultures. In the above code, if the culture were Turkish ("tr-TR"), the two strings would not compare equal. You might think that’s a minor problem, but consider what would happen if you wanted to prevent file URLs (i.e. file:\\) from being used in a program. Here’s how you might first write a method to check for such URLs:
static bool IsFileURL(string path)
{
return (string.Compare(path, 0, "FILE", 0, 5, true) == 0);
}
That would work in most cases, but in places where the capital of "i" is not "I", somebody could slip a "file:" URL past you with possibly serious security consequences. Why? Because String.Compare uses the current culture for comparisons.
How to Compare Culture-Invariant Strings
In versions of the .NET Framework prior to 2.0, the recommendation was to use the invariant culture for comparing culture-invariant strings. That way, you always knew that strings would be compared the same way, regardless of the current culture. The solution in .NET 1.1, then, was:
static bool IsFileUrl(string path)
{
return (string.Compare(path, 0, "FILE", 0, 5, true,
CultureInfo.InvariantCulture) == 0);
}
That works find when comparing ASCII strings, but InvariantCulture will sometimes make linguistic decisions that are not appropriate when character strings that should be treated as an array of bytes are instead interpreted. This can happen when comparing file names, cookies, and any other strings that can contain Unicode characters.
To alleviate these problems, .NET 2.0 introduces a new enumeration, StringComparison:
public enum StringComparison
{
CurrentCulture,
CurrentCultureIgnoreCase,
IvariantCulture,
InvariantCultureIgnoreCase,
Ordinal,
OrdinalIgnoreCase
}
New overloads of String.Compare and String.Equals allow you to specify which of the StringComparison types to use in the comparison. The new ordinal comparison types ignore the features of natural languages, and instead do byte-by-byte comparisons. For case-insensitive comparisons, the invariant culture’s character tables and casing rules are used.
The correct way to write our IsFileUrl method is with one of the new String.Compare overloads, like this:
static bool IsFileUrl(string path)
{
return (string.Compare(path, 0, "FILE", 0, 5,
StringComparison.OrdinalIgnoreCase) == 0);
}
The other benefit of using the ordinal comparisons is that they are fast. Much faster than the culture-sensitive or even the invariant culture comparisons. You should use them for all culture-agnostic string comparisons.
It’s important to note that the default comparison for String.Compare is the current culture. The default for String.Equals (including the == operator) is ordinal.
More Information
There is way more to comparing two strings than meets the eye. Naïve comparisons can cause no end of trouble and make your program fail in odd ways at unexpected times. Following the recommendations in the article New Recommendations for Using Strings in Microsoft .NET 2.0 will help keep you out of trouble.



Account Sign In
View your cart