다음을 통해 공유


.NET Framework에서 문자열 사용에 대한 유용한 정보

.NET Framework는 지역화 및 전역화된 응용 프로그램의 개발을 위한 광범위한 지원을 제공하며, 문자열 정렬 및 표시와 같은 일반적인 작업을 수행할 때 현재 문화권 또는 특정 문화권의 규칙을 쉽게 적용할 수 있게 합니다. 그러나 경우에 따라 문자열 정렬 또는 비교는 문화권을 구분하지 않는 작업일 수 있습니다. 예를 들어, 응용 프로그램에서 내부적으로 사용되는 문자열은 일반적으로 모든 문화권에 걸쳐 동일하게 처리됩니다. XML 태그, HTML 태그, 사용자 이름, 파일 경로 및 시스템 개체 이름과 같이 문화권에 대해 독립적인 문자열 데이터가 문화권을 구분하는 데이터로 해석될 경우 응용 프로그램 코드에 알아차리기 어려운 버그, 성능 저하, 그리고 경우에 따라 보안 문제가 발생할 수 있습니다.

이 항목에서는 .NET Framework 버전 4 및 그 이후 버전의 문자열 정렬, 비교 및 대/소문자 구분 메서드를 알아보고 적절한 문자열 처리 메서드를 선택하기 위한 권장 사항을 제시하고 문자열 처리 메서드에 대한 추가 정보를 제공합니다.

이 항목에는 다음과 같은 단원이 포함되어 있습니다.

  • 문자열 사용을 위한 권장 사항

  • 문자열 비교를 명시적으로 지정

  • 문자열 비교 세부 사항

  • 메서드 호출에 StringComparison 멤버 선택

  • .NET Framework의 일반적인 문자열 비교 메서드

  • 문자열 비교를 간접적으로 수행하는 메서드

문자열 사용을 위한 권장 사항

.NET Framework를 사용하여 개발할 때 문자열을 사용하는 경우에는 다음과 같은 간단한 권장 사항을 따르십시오.

문자열을 사용할 때 다음과 같은 사항에 주의해야 합니다.

  • 문자열 작업에 대한 문자열 비교 규칙을 명시적으로 또는 암시적으로 지정하지 않는 오버로드를 사용하지 말아야 합니다.

  • 대부분의 경우 StringComparison.InvariantCulture를 기반으로 하는 문자열 작업을 사용하지 말아야 합니다. 드물지만 예외적인 경우는 언어적으로 의미가 있지만 문화적으로는 관계가 없는 데이터를 유지하는 경우입니다.

  • 두 문자열이 같은지 여부를 확인하기 위해 String.Compare 또는 CompareTo 메서드의 오버로드를 사용하여 반환 값 0을 테스트하지 말아야 합니다.

맨 위로 이동

문자열 비교를 명시적으로 지정

.NET Framework의 문자열 조작 메서드는 대부분 오버로드됩니다. 일반적으로 하나 이상의 오버로드는 기본 설정을 수락하지만 다른 오버로드는 기본값을 수락하지 않고 그 대신 문자열을 비교 또는 조작하는 정확한 방법을 정의합니다. 기본값을 사용하지 않는 대부분의 메서드에는 StringComparison 형식의 매개 변수가 포함됩니다. 이 매개 변수는 문화권 또는 대/소문자별 문자열 비교를 위한 규칙을 명시적으로 지정하는 열거형입니다. 다음 표에는 StringComparison 열거형 멤버가 설명되어 있습니다.

StringComparison 멤버

설명

CurrentCulture

현재 문화권을 사용하여 대/소문자 구분 비교를 수행합니다.

CurrentCultureIgnoreCase

현재 문화권을 사용하여 대/소문자를 구분하지 않는 비교를 수행합니다.

InvariantCulture

고정 문화권을 사용하여 대/소문자 구분 비교를 수행합니다.

InvariantCultureIgnoreCase

고정 문화권을 사용하여 대/소문자를 구분하지 않는 비교를 수행합니다.

Ordinal

서수 비교를 수행합니다.

OrdinalIgnoreCase

대/소문자를 구분하지 않는 서수 비교를 수행합니다.

예를 들어, 문자 또는 문자열과 일치하는 String 개체의 부분 문자열 인덱스를 반환하는 IndexOf 메서드에는 다음과 같은 9개의 오버로드가 있습니다.

기본값을 사용하지 않는 오버로드를 선택하는 것이 좋습니다. 그 이유는 다음과 같습니다.

  • 문자열 인스턴스에서 Char를 검색하는 기본 매개 변수가 있는 일부 오버로드는 서수 비교를 수행하는 반면 문자열 인스턴스에서 문자열을 검색하는 다른 오버로드는 문화권을 구분합니다. 어떤 메서드가 어떤 기본값을 사용하는지 기억하기란 어렵고 오버로드를 혼동하기 쉽습니다.

  • 메서드 호출에 기본값을 사용하는 코드의 의도는 명확하지 않습니다. 기본값을 사용하는 다음 예제를 보면 개발자가 두 문자열에 대해 서수 비교와 언어적 비교 중 어느 쪽을 의도했는지, 또는 protocol과 "http" 간의 대/소문자 차이로 인해 같음 여부 테스트에서 false 반환이 발생하는지 여부를 알기가 어렵습니다.

    Dim protocol As String = GetProtocol(url)       
    If String.Equals(protocol, "http") Then
       ' ...Code to handle HTTP protocol.
    Else
       Throw New InvalidOperationException()
    End If   
    
    string protocol = GetProtocol(url);       
    if (String.Equals(protocol, "http")) {
       // ...Code to handle HTTP protocol.
    }
    else {
       throw new InvalidOperationException();
    }
    

일반적으로 기본값을 사용하지 않는 메서드를 호출하는 것이 좋습니다. 그렇게 하면 코드의 의도가 명확하게 드러나게 할 수 있기 때문입니다. 코드의 의도가 명확하게 드러나면 코드의 가독성이 향상되고 디버그와 유지 관리도 더 쉬워집니다. 다음 예제는 서수 비교가 사용된다는 점과 대/소문자 차이가 무시된다는 점이 명확히 드러난다는 차이를 제외하고는 이전 코드와 동일합니다.

Dim protocol As String = GetProtocol(url)       
If String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase) Then
   ' ...Code to handle HTTP protocol.
Else
   Throw New InvalidOperationException()
End If   
string protocol = GetProtocol(url);       
if (String.Equals(protocol, "http", StringComparison.OrdinalIgnoreCase)) {
   // ...Code to handle HTTP protocol.
}
else {
   throw new InvalidOperationException();
}

맨 위로 이동

문자열 비교 세부 사항

문자열 비교는 특히 정렬과 같음 여부 테스트를 포함한 많은 문자열 관련 작업의 핵심입니다. 문자열은 확실한 순서에 따라 정렬됩니다. 즉, 정렬된 문자열 목록에서 "my"가 "string" 앞에 나오는 경우 "my"는 "string"과 비교하여 더 작거나 같아야 합니다. 또한 비교는 같음을 암시적으로 정의합니다. 비교 작업에서는 같다고 판단된 문자열에 대해 0이 반환됩니다. 이에 대한 적절한 해석은 둘 중 어느 문자열도 다른 문자열에 비해 작지 않다는 것입니다. 문자열이 사용되는 가장 중요한 작업에는 다른 문자열과의 비교, 잘 정의된 정렬 작업 실행이라는 두 가지 절차 중 하나, 또는 두 가지 모두가 포함됩니다.

그러나 같음 여부 또는 정렬 순서에 대한 두 문자열 계산에서 하나의 올바른 결과가 나오는 것은 아닙니다. 결과는 문자열 비교에 사용되는 조건에 따라 달라집니다. 특히 서수 비교인 문자열 비교, 또는 현재 문화권이나 고정 문화권(영어를 기반으로 하는 로캘 무관 문화권)의 대/소문자 및 정렬 규칙을 기반으로 하는 문자열 비교에서는 다양한 결과가 나올 수 있습니다.

현재 문화권을 사용하는 문자열 비교

한 조건에서는 문자열을 비교할 때 현재 문화권의 규칙을 사용합니다. 현재 문화권을 기반으로 하는 비교에서는 스레드의 현재 문화권 또는 로캘을 사용합니다. 사용자가 문화권을 설정하지 않은 경우 제어판에 있는 국가별 옵션 창의 설정이 기본값이 됩니다. 데이터가 언어적으로 관련된 경우, 그리고 데이터가 문화권을 구분하는 사용자 상호 작용을 반영하는 경우 항상 현재 문화권을 기반으로 하는 비교를 사용해야 합니다.

.NET Framework의 비교 및 대/소문자 구분 동작은 문화권이 바뀌면 변경됩니다. 이러한 현상은 응용 프로그램이 개발된 컴퓨터와 문화권이 다른 컴퓨터에서 이 응용 프로그램이 실행되거나 실행 중인 스레드가 해당 문화권을 변경할 때 발생합니다. 이 동작은 의도된 것이지만 많은 개발자들은 이 부분이 명확하지 않다고 느낍니다. 다음 예제에서는 미국 영어("en-US")와 스웨덴어("sv-SE") 문화권 간의 정렬 순서 차이를 설명합니다. "ångström", "Windows" 및 "Visual Studio"라는 단어가 정렬된 문자열 배열에서 다른 위치에 나타나는 것을 볼 수 있습니다.

Imports System.Globalization
Imports System.Threading

Module Example
   Public Sub Main()
      Dim values() As String = { "able", "ångström", "apple", _
                                 "Æble", "Windows", "Visual Studio" }
      Array.Sort(values)
      DisplayArray(values)

      ' Change culture to Swedish (Sweden).
      Dim originalCulture As String = CultureInfo.CurrentCulture.Name
      Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
      Array.Sort(values)
      DisplayArray(values)

      ' Restore the original culture.
      Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
    End Sub

    Private Sub DisplayArray(values() As String)
      Console.WRiteLine("Sorting using the {0} culture:", _ 
                        CultureInfo.CurrentCulture.Name)
      For Each value As String In values
         Console.WriteLine("   {0}", value)
      Next
      Console.WriteLine()   
    End Sub
End Module
' The example displays the following output:
'       Sorting using the en-US culture:
'          able
'          Æble
'          ångström
'          apple
'          Visual Studio
'          Windows
'       
'       Sorting using the sv-SE culture:
'          able
'          Æble
'          apple
'          Windows
'          Visual Studio
'          ångström
using System;
using System.Globalization;
using System.Threading;

public class Example
{
   public static void Main()
   {
      string[] values= { "able", "ångström", "apple", "Æble", 
                         "Windows", "Visual Studio" };
      Array.Sort(values);
      DisplayArray(values);

      // Change culture to Swedish (Sweden).
      string originalCulture = CultureInfo.CurrentCulture.Name;
      Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
      Array.Sort(values);
      DisplayArray(values);

      // Restore the original culture.
      Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);
    }

    private static void DisplayArray(string[] values)
    {
      Console.WriteLine("Sorting using the {0} culture:",  
                        CultureInfo.CurrentCulture.Name);
      foreach (string value in values)
         Console.WriteLine("   {0}", value);

      Console.WriteLine();
    }
}
// The example displays the following output:
//       Sorting using the en-US culture:
//          able
//          Æble
//          ångström
//          apple
//          Visual Studio
//          Windows
//       
//       Sorting using the sv-SE culture:
//          able
//          Æble
//          apple
//          Windows
//          Visual Studio
//          ångström

현재 문화권을 사용하는, 대/소문자를 구분하지 않는 비교는 스레드의 현재 문화권에 지정된 대/소문자를 무시한다는 점을 제외하면 문화권 구분 비교와 동일합니다. 이 동작도 정렬 순서에서 명확하게 나타납니다.

현재 문화권의 의미 체계를 사용하는 비교는 다음과 같은 메서드에서 기본값입니다.

어느 경우든 메서드 호출의 의도를 명확하게 하기 위해 StringComparison 매개 변수가 있는 오버로드를 호출하는 것이 좋습니다.

비언어적인 문자열 데이터가 언어적으로 해석되거나 특정 문화권의 문자열 데이터가 다른 문화권의 규칙을 사용하여 해석되는 경우 모호하거나 모호하지 않은 여러 버그가 발생할 수 있습니다. 그 전형적인 예가 터키어 I 문제입니다.

미국 영어를 포함한 거의 모든 라틴어 알파벳에서 문자 "i"(\u0069)는 문자 "I"(\u0049)의 소문자 버전입니다. 해당 문화권에서 프로그래밍하는 사람은 별 생각 없이 이 대/소문자 구분 규칙을 기본값으로 사용합니다. 그러나 터키어("tr-TR") 알파벳에는 "점이 있는 I" 문자인 "İ"(\u0130)가 있으며, 이 문자가 "i"의 대문자 버전입니다. 또한 터키어에는 소문자로 "점이 없는 i"인 "ı"(\u0131)도 포함되며, 이 문자의 대문자 버전이 "I"입니다. 이러한 동작은 아제리어("az") 문화권에서도 나타납니다.

따라서 "i"의 대문자 또는 "I"의 소문자에 대한 가정은 일부 문화권에서는 유효하지 않습니다. 문자열 비교 루틴에 기본 오버로드를 사용하는 경우 이러한 비교 루틴은 문화권 간 차이에 따라 영향을 받게 됩니다. "file"과 "FILE" 문자열에 대해 대/소문자를 구분하지 않는 비교를 수행하려는 다음 시도에서 볼 수 있듯이, 비교할 데이터가 비언어적 데이터인 경우 기본 오버로드를 사용하면 바람직하지 않은 결과가 나올 수 있습니다.

Imports System.Globalization
Imports System.Threading

Module Example
   Public Sub Main()
      Dim fileUrl = "file"
      Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
      Console.WriteLine("Culture = {0}", _
                        Thread.CurrentThread.CurrentCulture.DisplayName)
      Console.WriteLine("(file == FILE) = {0}", _ 
                       fileUrl.StartsWith("FILE", True, Nothing))
      Console.WriteLine()

      Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
      Console.WriteLine("Culture = {0}", _
                        Thread.CurrentThread.CurrentCulture.DisplayName)
      Console.WriteLine("(file == FILE) = {0}", _ 
                        fileUrl.StartsWith("FILE", True, Nothing))
   End Sub
End Module
' The example displays the following output:
'       Culture = English (United States)
'       (file == FILE) = True
'       
'       Culture = Turkish (Turkey)
'       (file == FILE) = False
using System;
using System.Globalization;
using System.Threading;

public class Example
{
   public static void Main()
   {
      string fileUrl = "file";
      Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
      Console.WriteLine("Culture = {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      Console.WriteLine("(file == FILE) = {0}", 
                       fileUrl.StartsWith("FILE", true, null));
      Console.WriteLine();

      Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
      Console.WriteLine("Culture = {0}",
                        Thread.CurrentThread.CurrentCulture.DisplayName);
      Console.WriteLine("(file == FILE) = {0}", 
                        fileUrl.StartsWith("FILE", true, null));
   }
}
// The example displays the following output:
//       Culture = English (United States)
//       (file == FILE) = True
//       
//       Culture = Turkish (Turkey)
//       (file == FILE) = False

다음 예제와 같이 보안이 중요한 설정에 무심코 이 문화권을 사용하는 경우 이 비교는 심각한 문제를 일으킬 수 있습니다. IsFileURI("file:")와 같은 메서드 호출은 현재 문화권이 미국 영어인 경우 true를 반환하지만 현재 문화권이 터키어인 경우 false를 반환합니다. 따라서 터키어 시스템에서 누군가가 "FILE:"로 시작하는, 대/소문자를 구분하지 않는 URI에 대한 액세스를 차단하는 보안 방법을 속일 수 있습니다.

Public Shared Function IsFileURI(path As String) As Boolean 
   Return path.StartsWith("FILE:", True, Nothing)
End Function
public static bool IsFileURI(String path) 
{
   return path.StartsWith("FILE:", true, null);
}

이 경우 "file:"은 비언어적인, 문화권을 구분하지 않는 식별자로 해석되어야 하므로 코드는 다음 예제와 같이 작성되어야 합니다.

Public Shared Function IsFileURI(path As String) As Boolean 
    Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
public static bool IsFileURI(string path) 
{
   path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
   return true;
}

한편 앞의 예제에서는 같음 여부 테스트를 위해 String.StartsWith(String, StringComparison) 메서드를 사용합니다. 비교의 목적이 문자열 순서를 정하는 것이 아니라 같음 여부를 테스트하는 것이므로 더 나은 대안은 다음 예제와 같이 Equals 메서드를 호출하는 것입니다.

Public Shared Function IsFileURI(path As String) As Boolean
   If (path.Length < 5) Then Return False

   Return String.Equals(path.Substring(0, 5), "FILE:", _ 
                        StringComparison.OrdinalIgnoreCase)
End Function   
public static bool IsFileURI(string path)
{
   if (path.Length < 5) return false;

   return String.Equals(path.Substring(0, 5), "FILE:", 
                        StringComparison.OrdinalIgnoreCase);
}   

서수 문자열 작업

메서드 호출에 StringComparison.Ordinal 또는 StringComparison.OrdinalIgnoreCase 값을 지정하면 자연어의 특징이 무시되는 비언어적 비교가 지정됩니다. 이러한 StringComparison 값과 함께 호출되는 메서드는 대/소문자 구분 또는 문화권에 의해 매개 변수화된 같음 테이블 대신 간단한 바이트 비교를 기반으로 문자열 작업을 결정합니다. 대부분의 경우 이 방식은 문자열의 의도된 해석에 가장 잘 맞으면서 코드를 더 빠르고 안정적으로 만들어 줍니다.

서수 비교는 언어적 해석 없이 각 문자열의 각 바이트가 비교되는 문자열 비교입니다. 예를 들어, "windows"는 "Windows"와 일치하지 않습니다. 이는 본질적으로 C 런타임 strcmp 함수 호출입니다. 컨텍스트에서 문자열의 정확한 일치를 지정하거나 보수적인 일치 정책을 요구하는 경우 이 비교를 사용합니다. 또한 서수 비교는 결과를 판정할 때 언어적 규칙을 적용하지 않으므로 가장 빠른 비교 작업입니다.

.NET Framework에서 문자열에는 포함 null 문자가 포함될 수 있습니다. 서수 비교와 문화권 구분 비교(고정 문화권을 사용하는 비교 포함)의 가장 명확한 차이점 중 하나는 문자열의 포함 null 문자에 대한 처리와 관련됩니다. String.CompareString.Equals 메서드를 사용하여 문화권 구분 비교(고정 문화권을 사용하는 비교 포함)를 수행하는 경우 이러한 문자는 무시됩니다. 그 결과 문화권 구분 비교에서 포함 null 문자가 포함된 문자열은 이 문자가 포함되지 않은 문자열과 같은 문자열로 간주될 수 있습니다.

중요중요

문자열 비교 메서드에서 포함된 null 문자를 무시하더라도 String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOfString.StartsWith와 같은 문자열 검색 메서드는 이를 무시하지 않습니다.

다음 예제에서는 문자열 "Aa", 그리고 "A"와 "a" 사이에 여러 포함 null 문자가 포함된 비슷한 문자열 간의 문화권 구분 비교를 수행하고 두 문자열이 어떻게 같은 것으로 간주되는지 보여 줍니다.

Module Example
   Public Sub Main()
      Dim str1 As String = "Aa"
      Dim str2 As String = "A" + New String(Convert.ToChar(0), 3) + "a"
      Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
                        str1, ShowBytes(str1), str2, ShowBytes(str2))
      Console.WriteLine("   With String.Compare:")
      Console.WriteLine("      Current Culture: {0}", _
                        String.Compare(str1, str2, StringComparison.CurrentCulture))
      Console.WriteLine("      Invariant Culture: {0}", _
                        String.Compare(str1, str2, StringComparison.InvariantCulture))

      Console.WriteLine("   With String.Equals:")
      Console.WriteLine("      Current Culture: {0}", _
                        String.Equals(str1, str2, StringComparison.CurrentCulture))
      Console.WriteLine("      Invariant Culture: {0}", _
                        String.Equals(str1, str2, StringComparison.InvariantCulture))
   End Sub

   Private Function ShowBytes(str As String) As String
      Dim hexString As String = String.Empty
      For ctr As Integer = 0 To str.Length - 1
         Dim result As String = String.Empty
         result = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
         result = " " + result.Substring(0,2) + " " + result.Substring(2, 2)
         hexString += result
      Next
      Return hexString.Trim()
   End Function
End Module
using System;

public class Example
{
   public static void Main()
   {
      string str1 = "Aa";
      string str2 = "A" + new String('\u0000', 3) + "a";
      Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", 
                        str1, ShowBytes(str1), str2, ShowBytes(str2));
      Console.WriteLine("   With String.Compare:");
      Console.WriteLine("      Current Culture: {0}", 
                        String.Compare(str1, str2, StringComparison.CurrentCulture));
      Console.WriteLine("      Invariant Culture: {0}", 
                        String.Compare(str1, str2, StringComparison.InvariantCulture));

      Console.WriteLine("   With String.Equals:");
      Console.WriteLine("      Current Culture: {0}", 
                        String.Equals(str1, str2, StringComparison.CurrentCulture));
      Console.WriteLine("      Invariant Culture: {0}", 
                        String.Equals(str1, str2, StringComparison.InvariantCulture));
   }

   private static string ShowBytes(string str)
   {
      string hexString = String.Empty;
      for (int ctr = 0; ctr < str.Length; ctr++)
      {
         string result = String.Empty;
         result = Convert.ToInt32(str[ctr]).ToString("X4");
         result = " " + result.Substring(0,2) + " " + result.Substring(2, 2);
         hexString += result;
      }
      return hexString.Trim();
   }
}
// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Current Culture: 0
//          Invariant Culture: 0
//       With String.Equals:
//          Current Culture: True
//          Invariant Culture: True

단, 다음 예제에서 볼 수 있듯이 서수 비교를 하는 경우에는 이 두 문자열은 같은 것으로 간주되지 않습니다.

Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", _
                  str1, ShowBytes(str1), str2, ShowBytes(str2))
Console.WriteLine("   With String.Compare:")
Console.WriteLine("      Ordinal: {0}", _
                  String.Compare(str1, str2, StringComparison.Ordinal))

Console.WriteLine("   With String.Equals:")
Console.WriteLine("      Ordinal: {0}", _
                  String.Equals(str1, str2, StringComparison.Ordinal))
' The example displays the following output:
'    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
'       With String.Compare:
'          Ordinal: 97
'       With String.Equals:
'          Ordinal: False
Console.WriteLine("Comparing '{0}' ({1}) and '{2}' ({3}):", 
                  str1, ShowBytes(str1), str2, ShowBytes(str2));
Console.WriteLine("   With String.Compare:");
Console.WriteLine("      Ordinal: {0}", 
                  String.Compare(str1, str2, StringComparison.Ordinal));

Console.WriteLine("   With String.Equals:");
Console.WriteLine("      Ordinal: {0}", 
                  String.Equals(str1, str2, StringComparison.Ordinal));
// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Ordinal: 97
//       With String.Equals:
//          Ordinal: False

대/소문자 구분 서수 비교는 그 다음으로 가장 보수적인 방법입니다. 이러한 비교는 대부분의 대/소문자 구분을 무시합니다. 예를 들어, "windows"는 "Windows"와 일치합니다. ASCII 문자를 처리할 때 이 정책은 일반적인 ASCII 대/소문자 구분을 무시한다는 점을 제외하면 StringComparison.Ordinal과 동일합니다. 따라서 [A, Z](\u0041-\u005A)의 모든 문자는 [a,z](\u0061-\007A)의 해당 문자와 일치합니다. ASCII 범위 외의 대/소문자 구분은 고정 문화권의 테이블을 사용합니다. 따라서 다음 비교는

String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);

다음 비교와 동일합니다(속도는 더 빠름).

String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), 
               StringComparison.Ordinal)
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), 
               StringComparison.Ordinal);

이러한 비교는 여전히 상당히 빠릅니다.

참고참고

파일 시스템, 레지스트리 키 및 값, 환경 변수의 문자열 동작은 StringComparison.OrdinalIgnoreCase에서 가장 잘 나타납니다.

StringComparison.OrdinalStringComparison.OrdinalIgnoreCase 모두 이진 값을 직접 사용하며, 일치 여부를 확인할 때 가장 적합합니다. 비교 설정에 대해 잘 모르는 부분이 있는 경우 이 두 가지 값 중 하나를 사용합니다. 단, 이러한 값은 바이트 단위 비교를 수행하므로 언어적 정렬 순서(영어 사전과 같은)가 아닌 이진 정렬 순서에 따라 정렬됩니다. 사용자에게 결과를 표시할 경우 이 결과는 대부분의 컨텍스트에서 이상하게 보일 수 있습니다.

서수 의미 체계는 StringComparison 인수(같음 연산자 포함)가 포함되지 않은 String.Equals 오버로드의 기본값입니다. 어떤 경우든 StringComparison 매개 변수가 있는 오버로드를 호출하는 것이 좋습니다.

고정 문화권을 사용하는 문자열 작업

고정 문화권을 사용한 비교는 정적 CultureInfo.InvariantCulture 속성이 반환하는 CompareInfo 속성을 사용합니다. 이 동작은 모든 시스템에서 동일합니다. 즉, 범위 외의 모든 문자는 해당 고정 문자로 간주되는 문자로 변환됩니다. 이 정책은 여러 문화권에 걸쳐 하나의 문자열 동작 집합을 유지하는 데 유용할 수 있지만 예기치 않은 결과를 일으키는 경우가 많습니다.

고정 문화권을 사용하는, 대/소문자를 구분하지 않는 비교는 비교 정보에 대해서도 정적 CultureInfo.InvariantCulture 속성이 반환하는 정적 CompareInfo 속성을 사용합니다. 이러한 변환된 문자에서 모든 대/소문자 차이는 무시됩니다.

StringComparison.InvariantCultureStringComparison.Ordinal을 사용하는 비교는 ASCII 문자열에서 동일하게 작동합니다. 그러나 StringComparison.InvariantCulture는 바이트 집합으로 해석되어야 하는 문자열에 적합하지 않을 수 있는 언어적 결정을 내립니다. CultureInfo.InvariantCulture.CompareInfo 개체는 Compare 메서드에서 특정 문자 집합을 같은 것으로 해석하도록 합니다. 예를 들어, 다음 동등성은 고정 문화권에서 유효합니다.

InvariantCulture: a + ̊ = å

LATIN SMALL LETTER A 문자 "a"(\u0061)는 COMBINING RING ABOVE 문자 "+ " ̊" (\u030a) 옆에 있는 경우 LATIN SMALL LETTER A WITH RING ABOVE 문자 "å"(\u00e5)로 해석됩니다. 다음 예제에서 볼 수 있듯이 이 동작은 서수 비교와 다릅니다.

Dim separated As String = ChrW(&h61) + ChrW(&h30a)
Dim combined As String = ChrW(&he5)

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}", _
                  separated, combined, _
                  String.Compare(separated, combined, _ 
                                 StringComparison.InvariantCulture) = 0)

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}", _
                  separated, combined, _
                  String.Compare(separated, combined, _
                                 StringComparison.Ordinal) = 0)
' The example displays the following output:
'    Equal sort weight of a° and å using InvariantCulture: True
'    Equal sort weight of a° and å using Ordinal: False
string separated = "\u0061\u030a";
string combined = "\u00e5";

Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
                  separated, combined, 
                  String.Compare(separated, combined, 
                                 StringComparison.InvariantCulture) == 0);

Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
                  separated, combined,
                  String.Compare(separated, combined, 
                                 StringComparison.Ordinal) == 0);
// The example displays the following output:
//    Equal sort weight of a° and å using InvariantCulture: True
//    Equal sort weight of a° and å using Ordinal: False      

파일 이름, 쿠키 또는 "å"와 같은 조합이 나타날 수 있는 다른 항목을 해석하는 경우 서수 비교가 여전히 가장 투명하고 알맞은 동작을 제공합니다.

결국 고정 문화권에는 비교에 유용한 속성이 극소수뿐입니다. 언어적으로 관련된 방식으로 비교를 수행하지만(이로써 완전한 기호 동등성을 보장할 수 없음) 모든 문화권에서 표시하기에 적합한 방식은 아닙니다. 비교에 StringComparison.InvariantCulture를 사용하는 소수의 이유 중 하나는 여러 문화에 걸쳐 동일한 표시를 위해 정렬된 데이터를 유지하는 것입니다. 예를 들어, 표시를 위해 정렬된 식별자 목록이 포함된 대용량 데이터 파일이 응용 프로그램에 사용되는 경우 이 목록에 추가하려면 고정 스타일 정렬을 사용한 삽입이 필요합니다.

맨 위로 이동

메서드 호출에 StringComparison 멤버 선택

다음 표에서는 의미 체계 문자열 컨텍스트에서 StringComparison 열거형 멤버로의 매핑을 간략하게 보여 줍니다.

데이터

동작

해당 System.StringComparison

value

대/소문자 구분 내부 식별자

XML 및 HTTP와 같은 표준의 대/소문자 구분 식별자

대/소문자 구분 보안 관련 설정

바이트가 정확히 일치하는 비언어적 식별자

Ordinal

대/소문자를 구분하지 않는 내부 식별자

XML 및 HTTP와 같은 표준의 대/소문자를 구분하지 않는 식별자

파일 경로

레지스트리 키 및 값

환경 변수

리소스 식별자(예: 핸들 이름)

대/소문자를 구분하지 않는 보안 관련 설정

대/소문자와 관계없는 비언어적 식별자(특히 대부분의 Windows 시스템 서비스에 저장된 데이터)

OrdinalIgnoreCase

언어적으로 관련된 일부 지속 데이터

고정된 정렬 순서가 필요한 언어적 데이터의 표시

문화적으로 관계가 없지만 언어적으로 관련된 데이터

InvariantCulture

또는

InvariantCultureIgnoreCase

사용자에게 표시되는 데이터

대부분의 사용자 입력

지역의 언어적 관습이 필요한 데이터

CurrentCulture

또는

CurrentCultureIgnoreCase

맨 위로 이동

.NET Framework의 일반적인 문자열 비교 메서드

다음 단원에서는 문자열 비교에 가장 일반적으로 사용되는 메서드에 대해 설명합니다.

String.Compare

기본 해석: StringComparison.CurrentCulture

문자열 해석에서 가장 중심이 되는 작업으로, 이러한 메서드 호출의 모든 인스턴스를 검사하여 문자열을 현재 문화권에 따라 해석해야 하는지, 아니면 문화권에서 분리해야 하는지(기호적으로)를 판단해야 합니다. 일반적으로는 후자이며 StringComparison.Ordinal 비교를 대신 사용해야 합니다.

CultureInfo.CompareInfo 속성이 반환하는 System.Globalization.CompareInfo 클래스에도 CompareOptions 플래그 열거형을 통해 많은 수의 일치 옵션(서수, 공백 무시, 가나 형식 무시 등)을 제공하는 Compare 메서드가 포함됩니다.

String.CompareTo

기본 해석: StringComparison.CurrentCulture

현재 이 메서드는 StringComparison 형식을 지정하는 오버로드를 제공하지 않습니다. 일반적으로 이 메서드는 권장되는 String.Compare(String, String, StringComparison) 형식으로 변환할 수 있습니다.

IComparableIComparable<T> 인터페이스를 구현하는 형식은 이 메서드를 구현합니다. StringComparison 매개 변수 옵션을 제공하지 않으므로 많은 경우 사용자가 각자의 생성자에서 StringComparer를 지정할 수 있습니다. 다음 예제에서는 클래스 생성자에 StringComparer 매개 변수가 포함되는 FileName 클래스를 정의합니다. 그러면 이 StringComparer 개체는 FileName.CompareTo 메서드에 사용됩니다.

Public Class FileName : Implements IComparable
   Dim fname As String
   Dim comparer As StringComparer 

   Public Sub New(name As String, comparer As StringComparer)
      If String.IsNullOrEmpty(name) Then
         Throw New ArgumentNullException("name")
      End If

      Me.fname = name

      If comparer IsNot Nothing Then
         Me.comparer = comparer
      Else
         Me.comparer = StringComparer.OrdinalIgnoreCase
      End If      
   End Sub

   Public ReadOnly Property Name As String
      Get
         Return fname
      End Get   
   End Property

   Public Function CompareTo(obj As Object) As Integer _
          Implements IComparable.CompareTo
      If obj Is Nothing Then Return 1

      If Not TypeOf obj Is FileName Then
         obj = obj.ToString()
      Else
         obj = CType(obj, FileName).Name
      End If         
      Return comparer.Compare(Me.fname, obj)
   End Function
End Class
using System;

public class FileName : IComparable
{
   string fname;
   StringComparer comparer; 

   public FileName(string name, StringComparer comparer)
   {
      if (String.IsNullOrEmpty(name))
         throw new ArgumentNullException("name");

      this.fname = name;

      if (comparer != null)
         this.comparer = comparer;
      else
         this.comparer = StringComparer.OrdinalIgnoreCase;
   }

   public string Name
   {
      get { return fname; }
   }

   public int CompareTo(object obj)
   {
      if (obj == null) return 1;

      if (! (obj is FileName))
         return comparer.Compare(this.fname, obj.ToString());
      else
         return comparer.Compare(this.fname, ((FileName) obj).Name);
   }
}

String.Equals

기본 해석: StringComparison.Ordinal

String 클래스는 정적 또는 인스턴스 Equals 메서드 오버로드를 호출하거나 정적 같음 연산자를 사용하여 같음 여부를 테스트할 수 있도록 합니다. 오버로드와 연산자는 기본적으로 서수 비교를 사용합니다. 그러나 서수 비교를 수행하려는 경우라도 StringComparison 형식을 명시적으로 지정하는 오버로드를 호출하는 것이 좋습니다. 이렇게 하면 코드에서 특정 문자열 해석을 검색하기가 더 쉽습니다.

String.ToUpper 및 String.ToLower

기본 해석: StringComparison.CurrentCulture

문자열을 대문자 또는 소문자로 강제하는 것은 대/소문자에 관계없이 문자열을 비교하기 위한 평준화로 사용되는 경우가 많으므로 이 메서드를 사용할 때는 주의해야 합니다. 이러한 경우에는 대/소문자를 구분하지 않는 비교를 사용하는 방법을 고려하십시오.

String.ToUpperInvariantString.ToLowerInvariant 메서드도 사용할 수 있습니다. ToUpperInvariant는 대/소문자를 정규화하기 위한 표준적인 방법입니다. StringComparison.OrdinalIgnoreCase를 사용한 비교는 동작 측면에서 두 호출의 합성입니다. 즉, 두 문자열 인수에 대해 ToUpperInvariant를 호출하고 StringComparison.Ordinal을 사용하여 비교를 수행하는 것입니다.

오버로드도 특정 문화권에서 대문자 및 소문자로 변환하는 데 사용할 수 있습니다. 해당 문화권을 나타내는 CultureInfo를 메서드에 전달하면 됩니다.

Char.ToUpper 및 Char.ToLower

기본 해석: StringComparison.CurrentCulture

이 메서드는 이전 단원에서 설명한 String.ToUpperString.ToLower 메서드와 비슷하게 작동합니다.

String.StartsWith 및 String.EndsWith

기본 해석: StringComparison.CurrentCulture

기본적으로 이 두 메서드는 모두 문화권 구분 비교를 수행합니다.

String.IndexOf 및 String.LastIndexOf

기본 해석: StringComparison.CurrentCulture

이러한 메서드의 기본 오버로드가 비교를 수행하는 방법은 일관적이지 않습니다. Char 매개 변수가 포함된 모든 String.IndexOfString.LastIndexOf 메서드는 서수 비교를 수행하지만 String 매개 변수가 포함된 기본 String.IndexOfString.LastIndexOf 메서드는 문화권 구분 비교를 수행합니다.

String.IndexOf(String) 또는 String.LastIndexOf(String) 메서드를 호출하고 여기에 현재 인스턴스에서 찾을 문자열을 전달하는 경우 StringComparison 형식을 명시적으로 지정하는 오버로드를 호출하는 것이 좋습니다. Char 인수가 포함된 오버로드는 StringComparison 서식 지정을 허용하지 않습니다.

맨 위로 이동

문자열 비교를 간접적으로 수행하는 메서드

중심 작업이 문자열 비교인 일부 문자열이 아닌 메서드는 StringComparer 형식을 사용합니다. StringComparer 클래스에는 해당 StringComparer.Compare 메서드가 다음과 같은 유형의 문자열 비교를 수행하는 StringComparer 인스턴스를 반환하는 6개의 정적 속성이 포함됩니다.

Array.Sort 및 Array.BinarySearch

기본 해석: StringComparison.CurrentCulture

컬렉션에 데이터를 저장하거나 파일 또는 데이터베이스에서 컬렉션으로 보관된 데이터를 읽어들일 때 현재 문화권을 전환할 경우 컬렉션의 고정이 무효화될 수 있습니다. Array.BinarySearch 메서드는 검색할 배열의 요소가 이미 정렬되어 있다고 가정합니다. 배열의 문자열 요소를 정렬하기 위해 Array.Sort 메서드는 String.Compare 메서드를 호출하여 개별 요소에 순서를 지정합니다. 배열이 정렬되는 시간과 배열의 내용이 검색되는 시간 사이에 문화권이 바뀌는 경우 문화권 구분 비교자를 사용하면 위험할 수 있습니다. 예를 들어, 다음 코드에서 저장 및 검색은 Thread.CurrentThread.CurrentCulture 속성에 의해 암시적으로 제공된 비교자에서 수행됩니다. StoreNames와 DoesNameExist 호출 사이에 문화권이 변경되고 특히 배열 내용이 두 메서드 호출 사이에 지속되는 경우 이진 검색이 실패할 수 있습니다.

' Incorrect.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names)          ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name) >= 0      ' Line B.
End Function
// Incorrect.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names); // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name) >= 0);  // Line B.
}

다음 예제에서 권장되는 변형을 볼 수 있습니다. 이 예제에서는 배열을 정렬하고 검색하는 데 동일한 서수(문화권을 구분하지 않음) 비교 메서드를 사용합니다. 변경 코드는 두 예제에서 Line A 및 Line B 레이블이 표시된 줄에 반영되어 있습니다.

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names, StringComparer.Ordinal)           ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name, StringComparer.Ordinal) >= 0      ' Line B.
End Function
// Correct.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names, StringComparer.Ordinal);  // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name, StringComparer.Ordinal) >= 0);  // Line B.
}

이 데이터가 유지되어 문화권 간에 이동되고 이 데이터를 사용자에게 제공하기 위해 정렬이 사용되는 경우 언어적으로 작동하여 사용자 출력을 더 향상시키면서 문화권 변경의 영향을 받지 않는 StringComparison.InvariantCulture 사용을 고려할 수 있습니다. 다음 예제에서는 배열을 정렬하고 검색하는 데 고정 문화권을 사용하도록 앞의 두 예제를 수정합니다.

' Correct.
Dim storedNames() As String

Public Sub StoreNames(names() As String)
   Dim index As Integer = 0
   ReDim storedNames(names.Length - 1)

   For Each name As String In names
      Me.storedNames(index) = name
      index+= 1
   Next

   Array.Sort(names, StringComparer.InvariantCulture)           ' Line A.
End Sub

Public Function DoesNameExist(name As String) As Boolean
   Return Array.BinarySearch(Me.storedNames, name, StringComparer.InvariantCulture) >= 0      ' Line B.
End Function
// Correct.
string []storedNames;

public void StoreNames(string [] names)
{
   int index = 0;
   storedNames = new string[names.Length];

   foreach (string name in names)
   {
      this.storedNames[index++] = name;
   }

   Array.Sort(names, StringComparer.InvariantCulture);  // Line A.
}

public bool DoesNameExist(string name)
{
   return (Array.BinarySearch(this.storedNames, name, StringComparer.InvariantCulture) >= 0);  // Line B.
}

컬렉션 예제: Hashtable 생성자

문자열 해시는 문자열 비교 방법에 따라 영향을 받는 작업의 다른 예를 제공합니다.

다음 예제에서는 StringComparer.OrdinalIgnoreCase 속성이 반환하는 StringComparer 개체를 전달하여 Hashtable 개체를 인스턴스화합니다. StringComparer에서 파생된 클래스 StringComparerIEqualityComparer 인터페이스를 구현하므로 GetHashCode 메서드는 해시 테이블의 문자열 해시 코드를 계산하는 데 사용됩니다.

Const initialTableCapacity As Integer = 100
Dim h As Hashtable

Public Sub PopulateFileTable(dir As String)
   h = New Hashtable(initialTableCapacity, _
                     StringComparer.OrdinalIgnoreCase)

   For Each filename As String In Directory.GetFiles(dir)
      h.Add(filename, File.GetCreationTime(filename))
   Next                        
End Sub

Public Sub PrintCreationTime(targetFile As String)
   Dim dt As Object = h(targetFile)
   If dt IsNot Nothing Then
      Console.WriteLine("File {0} was created at {1}.", _
         targetFile, _
         CDate(dt))
   Else
      Console.WriteLine("File {0} does not exist.", targetFile)
   End If
End Sub  
const int initialTableCapacity = 100;
Hashtable h;

public void PopulateFileTable(string directory)
{
   h = new Hashtable(initialTableCapacity, 
                     StringComparer.OrdinalIgnoreCase);

   foreach (string file in Directory.GetFiles(directory))
         h.Add(file, File.GetCreationTime(file));
}

public void PrintCreationTime(string targetFile)
{
   Object dt = h[targetFile];
   if (dt != null)
   {
      Console.WriteLine("File {0} was created at time {1}.",
         targetFile, 
         (DateTime) dt);
   }
   else
   {
      Console.WriteLine("File {0} does not exist.", targetFile);
   }
}

맨 위로 이동