.NET은 지역화되고 세계화된 애플리케이션을 개발하기 위한 광범위한 지원을 제공하며, 문자열 정렬 및 표시와 같은 일반적인 작업을 수행할 때 현재 문화권 또는 특정 문화권의 규칙을 쉽게 적용할 수 있습니다. 그러나 문자열을 정렬하거나 비교하는 것이 항상 문화권을 구분하는 작업은 아닙니다. 예를 들어 애플리케이션에서 내부적으로 사용되는 문자열은 일반적으로 모든 문화권에서 동일하게 처리되어야 합니다. XML 태그, HTML 태그, 사용자 이름, 파일 경로 및 시스템 개체 이름과 같은 문화적으로 독립적인 문자열 데이터가 문화권을 구분하는 것처럼 해석되는 경우 애플리케이션 코드는 미묘한 버그, 성능 저하 및 경우에 따라 보안 문제가 발생할 수 있습니다.
이 문서에서는 .NET의 문자열 정렬, 비교 및 대/소문자 구분 메서드를 검토하고, 적절한 문자열 처리 메서드를 선택하기 위한 권장 사항을 제시하며, 문자열 처리 메서드에 대한 추가 정보를 제공합니다.
문자열 사용에 대한 권장 사항
.NET을 사용하여 개발할 때 문자열을 비교할 때 다음 권장 사항을 따릅니다.
팁 (조언)
다양한 문자열 관련 메서드가 비교를 수행합니다. 예를 들어 String.Equals, , String.CompareString.IndexOf및 String.StartsWith가 있습니다.
- 문자열 작업에 대한 문자열 비교 규칙을 명시적으로 지정하는 오버로드를 사용합니다. 일반적으로 형식의 매개 변수 StringComparison가 있는 메서드 오버로드를 호출하는 작업이 포함됩니다.
- StringComparison.Ordinal 또는 StringComparison.OrdinalIgnoreCase을 문화권에 구애받지 않은 문자열 일치의 안전한 기본값으로 사용하십시오.
- StringComparison.Ordinal 또는 StringComparison.OrdinalIgnoreCase을 비교에 사용하여 성능을 향상시키십시오.
- 출력을 사용자에게 표시할 때 StringComparison.CurrentCulture에 기반한 문자열 작업을 사용합니다.
- 비교가 언어적으로 관련이 없는 경우(예: 기호 StringComparison.Ordinal)에는 문자열 작업 대신 비언어적 StringComparison.OrdinalIgnoreCase 또는 CultureInfo.InvariantCulture 값을 사용하세요.
- 문자열을 비교하기 위해 정규화할 때 String.ToUpperInvariant 메서드 대신 String.ToLowerInvariant 메서드를 사용하십시오.
- 메서드의 오버로드를 사용하여 두 문자열이 String.Equals 같은지 여부를 테스트합니다.
- 문자열의 같음을 확인하지 않고, 문자열을 정렬하기 위해 String.Compare 및 String.CompareTo 메서드를 사용하세요.
- 문화권 구분 서식을 사용하여 숫자 및 날짜와 같은 문자열이 아닌 데이터를 사용자 인터페이스에 표시합니다. 고정 문화권 서식을 사용하여 문자열이 아닌 데이터를 문자열 형식으로 유지합니다.
문자열을 비교할 때는 다음 방법을 사용하지 않습니다.
- 문자열 작업에 대한 문자열 비교 규칙을 명시적으로 또는 암시적으로 지정하지 않는 오버로드를 사용하지 마세요.
- 대부분의 경우를 기준으로 StringComparison.InvariantCulture 문자열 작업을 사용하지 마세요. 몇 가지 예외 중 하나는 언어적으로 의미 있지만 문화적으로 중립적인 데이터를 유지하는 경우입니다.
- 또는 String.Compare 메서드의 CompareTo 오버로드를 사용하고 0의 반환 값을 테스트하여 두 문자열이 같은지 여부를 확인하지 마세요.
명시적으로 문자열 비교 지정
.NET의 문자열 조작 메서드 대부분은 오버로드됩니다. 일반적으로 하나 이상의 오버로드는 기본 설정을 허용하는 반면, 다른 오버로드는 기본값을 허용하지 않고 대신 문자열을 비교하거나 조작할 정확한 방법을 정의합니다. 기본값을 사용하지 않는 대부분의 메서드에는 문화권 및 대/소문자별 문자열 비교에 대한 규칙을 명시적으로 지정하는 열거형인 형식 StringComparison의 매개 변수가 포함됩니다. 다음 표에서는 열거형 멤버에 StringComparison 대해 설명합니다.
StringComparison 멤버 | 설명 |
---|---|
CurrentCulture | 현재 문화를 사용하여 대/소문자를 구분하여 비교합니다. |
CurrentCultureIgnoreCase | 현재 문화에 따라 대/소문자를 구분하지 않고 비교를 수행합니다. |
InvariantCulture | 불변 문화 구분을 사용하여 대/소문자를 구분하는 비교를 수행합니다. |
InvariantCultureIgnoreCase | 고정 문화권을 사용하여 대/소문자를 구분하지 않는 비교를 수행합니다. |
Ordinal | 서수 비교를 수행합니다. |
OrdinalIgnoreCase | 대/소문자를 구분하지 않는 순서 비교를 수행합니다. |
예를 들어 IndexOf 문자 또는 문자열과 일치하는 개체에서 String 부분 문자열의 인덱스를 반환하는 메서드에는 9개의 오버로드가 있습니다.
- IndexOf(Char), IndexOf(Char, Int32)및 IndexOf(Char, Int32, Int32)- 기본적으로 문자열의 문자에 대한 서수(대/소문자를 구분하고 문화권을 구분하지 않는) 검색을 수행합니다.
- IndexOf(String), IndexOf(String, Int32), 및 IndexOf(String, Int32, Int32)는 기본적으로 문자열 내에서 대/소문자와 문화권을 구분하여 부분 문자열을 검색합니다.
- IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison)및 IndexOf(String, Int32, Int32, StringComparison)- 비교 형식을 지정할 수 있는 형식 StringComparison 의 매개 변수를 포함합니다.
다음과 같은 이유로 기본값을 사용하지 않는 오버로드를 선택하는 것이 좋습니다.
기본 매개 변수로 지정된 일부 오버로드(문자열 인스턴스에서 Char를 검색하는 경우)는 서수 방식으로 비교를 수행하는 반면, 다른 오버로드는 문자열 인스턴스에서 문자열을 검색할 때 문화권에 민감한 방식으로 수행됩니다. 어떤 메서드가 어떤 기본값을 사용하는지 기억하기 어렵고 오버로드를 혼동하기 쉽습니다.
메서드 호출의 기본값을 사용하는 코드의 의도는 명확하지 않습니다. 기본값을 사용하는 다음 예제에서는 개발자가 실제로 두 문자열의 서수 또는 언어 비교를 의도했는지, 아니면 대/소문자와 "https"의
url.Scheme
대/소문자 차이로 인해 같음 테스트가 반환false
되는지 여부를 알기 어렵습니다.Uri url = new("https://learn.microsoft.com/"); // Incorrect if (string.Equals(url.Scheme, "https")) { // ...Code to handle HTTPS protocol. }
Dim url As New Uri("https://learn.microsoft.com/") ' Incorrect If String.Equals(url.Scheme, "https") Then ' ...Code to handle HTTPS protocol. End If
일반적으로 코드의 의도를 명확하게 만들기 때문에 기본값을 사용하지 않는 메서드를 호출하는 것이 좋습니다. 이렇게 하면 코드를 더 읽기 쉽고 쉽게 디버그하고 유지 관리할 수 있습니다. 다음 예제에서는 이전 예제에 대해 제기된 질문을 다룹니다. 서수 비교가 사용되고 경우에 따른 차이가 무시됨을 분명히 합니다.
Uri url = new("https://learn.microsoft.com/");
// Correct
if (string.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
// ...Code to handle HTTPS protocol.
}
Dim url As New Uri("https://learn.microsoft.com/")
' Incorrect
If String.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase) Then
' ...Code to handle HTTPS protocol.
End If
문자열 비교의 세부 정보
문자열 비교는 많은 문자열 관련 작업, 특히 정렬 및 같음 테스트의 핵심입니다. 문자열은 정해진 순서로 정렬됩니다. 정렬된 문자열 목록에서 "my"가 "string" 앞에 나타나면 "my"는 "string"보다 작거나 같아야 합니다. 또한 비교는 같음을 암시적으로 정의합니다. 비교 작업은 같음으로 간주되는 문자열에 대해 0을 반환합니다. 좋은 해석은 두 문자열이 다른 문자열보다 작지 않는다는 것입니다. 문자열과 관련된 가장 의미 있는 작업에는 다른 문자열과 비교하고 잘 정의된 정렬 작업을 실행하는 절차 중 하나 또는 둘 다 포함됩니다.
비고
정렬 가중치 테이블, Windows 운영 체제의 정렬 및 비교 작업에 사용되는 문자 가중치에 대한 정보가 포함된 텍스트 파일 집합 및 Linux 및 macOS용 정렬 가중치 테이블의 최신 버전인 기본 유니코드 데이터 정렬 요소 테이블을 다운로드할 수 있습니다. Linux 및 macOS에서 정렬 가중치 테이블의 특정 버전은 시스템에 설치된 유니코드용 International Components 라이브러리의 버전에 따라 달라집니다. ICU 버전 및 구현하는 유니코드 버전에 대한 자세한 내용은 ICU 다운로드를 참조하세요.
그러나 같음 또는 정렬 순서에 대해 두 문자열을 평가해도 올바른 단일 결과가 생성되지는 않습니다. 결과는 문자열을 비교하는 데 사용되는 조건에 따라 달라집니다. 특히 서수 비교이거나 현재 문화권 또는 고정된 문화 권(영어 기반의 로캘 비의존적 문화권)의 대/소문자 구분 및 정렬 규칙을 기반으로 하는 문자열 비교는 서로 다른 결과를 생성할 수 있습니다.
또한 다른 버전의 .NET을 사용하거나 다른 운영 체제 또는 운영 체제 버전에서 .NET을 사용하는 문자열 비교는 다른 결과를 반환할 수 있습니다. 자세한 내용은 문자열 및 유니코드 표준을 참조하세요.
현재 문화에 맞춰 문자열 비교
한 가지 기준은 문자열을 비교할 때 현재 문화권의 규칙을 사용하는 것입니다. 현재 문화권을 기반으로 하는 비교는 스레드의 현재 문화권 또는 로캘을 사용합니다. 문화 설정이 사용자가 설정하지 않으면 기본적으로 운영 체제의 설정으로 지정됩니다. 데이터가 언어적으로 관련된 경우와 문화권에 민감한 사용자 상호 작용을 반영하는 경우 현재 문화권을 기반으로 하는 비교를 항상 사용해야 합니다.
그러나 .NET의 비교와 대소문자 처리 방식은 지역이나 언어 설정이 변경될 때 변합니다. 애플리케이션이 개발된 컴퓨터와 문화권이 다른 컴퓨터에서 애플리케이션을 실행하거나 실행 중인 스레드가 문화권을 변경할 때 발생합니다. 이 동작은 의도적이지만 많은 개발자에게 명확하지 않습니다. 다음 예제에서는 미국 영어("en-US")와 스웨덴어("sv-SE") 문화권 간의 정렬 순서 차이를 보여 줍니다. "ångström", "Windows" 및 "Visual Studio"라는 단어는 정렬된 문자열 배열의 다른 위치에 표시됩니다.
using System.Globalization;
// Words to sort
string[] values= { "able", "ångström", "apple", "Æble",
"Windows", "Visual Studio" };
// Current culture
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);
static void DisplayArray(string[] values)
{
Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:");
foreach (string value in values)
Console.WriteLine($" {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
// apple
// Visual Studio
// Windows
// ångström
// Æble
Imports System.Globalization
Imports System.Threading
Module Program
Sub Main()
' Words to sort
Dim values As String() = {"able", "ångström", "apple", "Æble",
"Windows", "Visual Studio"}
' Current culture
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
Sub DisplayArray(values As String())
Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:")
For Each value As String In values
Console.WriteLine($" {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
' apple
' Visual Studio
' Windows
' ångström
' Æble
현재 문화권을 사용하는 대소문자 구분 없는 비교는 문화권에 민감한 비교와 동일합니다. 다만, 이는 스레드의 현재 문화권에 따라 대소문자를 무시합니다. 이 동작은 정렬 순서에서도 나타날 수 있습니다.
현재 문화권 의미 체계를 사용하는 비교는 다음 메서드의 기본값입니다.
- String.Compare 매개 변수를 포함하지 않는 오버로드입니다 StringComparison .
- String.CompareTo 과부하.
- 기본 String.StartsWith(String) 메서드와 String.StartsWith(String, Boolean, CultureInfo)
null
CultureInfo 매개 변수가 있는 메서드. - 기본 String.EndsWith(String) 메서드와 String.EndsWith(String, Boolean, CultureInfo)
null
CultureInfo 매개 변수가 있는 메서드. - String.IndexOf 오버로드는 검색 매개 변수로 String을 허용하며, StringComparison 매개 변수가 없습니다.
- String.LastIndexOf 오버로드는 검색 매개 변수로 String을 허용하며, StringComparison 매개 변수가 없습니다.
어떤 경우든 메서드 호출의 의도를 명확하게 하기 위해 매개 변수가 있는 StringComparison 오버로드를 호출하는 것이 좋습니다.
비언어적 문자열 데이터가 언어적으로 해석되거나 특정 문화권의 문자열 데이터가 다른 문화권의 규칙을 사용하여 해석될 때 미묘하고 미묘한 버그가 나타날 수 있습니다. 정식 예제는 Turkish-I 문제입니다.
미국 영어를 포함한 거의 모든 라틴어 알파벳의 경우 문자 "i"(\u0069)는 문자 "I"(\u0049)의 소문자 버전입니다. 이 대/소문자 구분 규칙은 이러한 문화권에서 프로그래밍하는 사용자의 기본값이 됩니다. 그러나 터키어("tr-TR") 알파벳에는 "i"의 대문자 버전인 "점이 있는 I" 문자 "ý"(\u0130)가 포함됩니다. 터키어에 소문자 '점 없는 i' 문자인 'ı'(\u0131)가 포함되어 있고, 대문자로 변환될 때 'I'가 됩니다. 이 동작은 아제르바이잔어("az") 문화권에서도 발생합니다.
따라서 "i"를 대문자로 사용하거나 "I"를 낮추는 것에 대한 가정은 모든 문화권에서 유효하지 않습니다. 문자열 비교 루틴에 기본 오버로드를 사용하는 경우 문화권 간에 차이가 발생합니다. 비교할 데이터가 비언어적이면 다음 시도에서 "bill" 및 "BILL" 문자열을 대/소문자를 구분하지 않는 비교를 수행하려고 할 때 기본 오버로드를 사용하면 바람직하지 않은 결과를 생성할 수 있습니다.
using System.Globalization;
string name = "Bill";
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
Console.WriteLine();
Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
//' The example displays the following output:
//'
//' Culture = English (United States)
//' Is 'Bill' the same as 'BILL'? True
//' Does 'Bill' start with 'BILL'? True
//'
//' Culture = Turkish (Türkiye)
//' Is 'Bill' the same as 'BILL'? True
//' Does 'Bill' start with 'BILL'? False
Imports System.Globalization
Imports System.Threading
Module Program
Sub Main()
Dim name As String = "Bill"
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
Console.WriteLine()
Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
Console.WriteLine($" Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
Console.WriteLine($" Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
End Sub
End Module
' The example displays the following output:
'
' Culture = English (United States)
' Is 'Bill' the same as 'BILL'? True
' Does 'Bill' start with 'BILL'? True
'
' Culture = Turkish (Türkiye)
' Is 'Bill' the same as 'BILL'? True
' Does 'Bill' start with 'BILL'? False
이 비교는 다음 예제와 같이 문화권이 보안에 민감한 설정에서 실수로 사용되는 경우 심각한 문제를 일으킬 수 있습니다. 현재 문화권이 미국 영어인 경우 IsFileURI("file:")
메서드를 호출하면 true
을 반환하지만, 현재 문화권이 터키어인 경우 false
을 반환합니다. 따라서 터키 시스템에서는 누군가가 "FILE:"로 시작하는 대/소문자를 구분하지 않는 URI에 대한 액세스를 차단하는 보안 조치를 우회할 수 있습니다.
public static bool IsFileURI(string path) =>
path.StartsWith("FILE:", true, null);
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", True, Nothing)
End Function
이 경우 "file:"은 비언어적이고 문화권을 구분하지 않는 식별자로 해석되므로 다음 예제와 같이 코드를 대신 작성해야 합니다.
public static bool IsFileURI(string path) =>
path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
Public Shared Function IsFileURI(path As String) As Boolean
Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function
서수 문자열 처리
메서드 호출에서 StringComparison.Ordinal 값 또는 StringComparison.OrdinalIgnoreCase 값을 지정하면 자연어의 기능이 무시되는 비언어적 비교를 의미합니다. 이러한 StringComparison 값으로 호출되는 메서드는 문화 기준이 있는 대/소문자 또는 동등 테이블을 이용하는 대신 단순한 바이트 비교에 기반해서 문자열 연산 결정을 내립니다. 대부분의 경우 이 방법은 코드를 더 빠르고 안정적으로 만들면서 의도한 문자열 해석에 가장 적합합니다.
순서 비교는 각 문자열의 각 바이트를 언어 해석 없이 비교하는 작업입니다. 예를 들어 "windows"는 "Windows"와 일치하지 않는다. 이는 기본적으로 C 런타임 strcmp
함수에 대한 호출입니다. 컨텍스트에서 문자열을 정확히 일치시켜야 한다고 지시하거나 보수적인 일치 정책을 요구할 때 이 비교를 사용합니다. 또한 서수 비교는 결과를 결정할 때 언어 규칙을 적용하지 않으므로 가장 빠른 비교 작업입니다.
.NET의 문자열에는 포함된 null 문자(및 기타 인쇄하지 않는 문자)가 포함될 수 있습니다. 서수 비교와 문화에 민감한 비교(고정 문화권을 사용하는 비교 포함)의 가장 명확한 차이점 중 하나는 문자열에 포함된 null 문자를 처리하는 방식에 관련이 있습니다. 이러한 문자는 문화권 구분 비교(고정 문화권을 사용하는 비교 포함)를 수행하기 위해 String.Compare 및 String.Equals 메서드를 사용할 때 무시됩니다. 따라서 포함된 null 문자가 포함된 문자열은 그렇지 않은 문자열과 같은 것으로 간주될 수 있습니다. 포함된 인쇄되지 않은 문자는 다음과 같은 String.StartsWith문자열 비교 메서드를 위해 건너뛸 수 있습니다.
중요합니다
문자열 비교 메서드는 포함된 null 문자를 무시하지만, 문자열 검색 메서드 String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf, String.StartsWith는 무시하지 않습니다.
다음 예제에서는 "A"와 "a" 사이에 포함된 Null 문자가 여러 개 포함된 유사한 문자열을 사용하여 문자열 "Aa"의 문화권 구분 비교를 수행하고 두 문자열이 동일한 것으로 간주되는 방법을 보여 줍니다.
string str1 = "Aa";
string str2 = "A" + new string('\u0000', 3) + "a";
Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-us");
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine(" With String.Compare:");
Console.WriteLine($" Current Culture: {string.Compare(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($" Invariant Culture: {string.Compare(str1, str2, StringComparison.InvariantCulture)}");
Console.WriteLine(" With String.Equals:");
Console.WriteLine($" Current Culture: {string.Equals(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($" Invariant Culture: {string.Equals(str1, str2, StringComparison.InvariantCulture)}");
string ShowBytes(string value)
{
string hexString = string.Empty;
for (int index = 0; index < value.Length; index++)
{
string result = Convert.ToInt32(value[index]).ToString("X4");
result = string.Concat(" ", 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 'Aa' (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
Module Program
Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
Console.WriteLine(" With String.Compare:")
Console.WriteLine($" Current Culture: {String.Compare(str1, str2, StringComparison.CurrentCulture)}")
Console.WriteLine($" Invariant Culture: {String.Compare(str1, str2, StringComparison.InvariantCulture)}")
Console.WriteLine(" With String.Equals:")
Console.WriteLine($" Current Culture: {String.Equals(str1, str2, StringComparison.CurrentCulture)}")
Console.WriteLine($" Invariant Culture: {String.Equals(str1, str2, StringComparison.InvariantCulture)}")
End Sub
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 = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
hexString &= result
Next
Return hexString.Trim()
End Function
' The example displays the following output:
' Comparing 'Aa' (00 41 00 61) and 'Aa' (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
End Module
그러나 다음 예제와 같이 서수 비교를 사용할 때는 문자열이 같은 것으로 간주되지 않습니다.
string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine(" With String.Compare:");
Console.WriteLine($" Ordinal: {string.Compare(str1, str2, StringComparison.Ordinal)}");
Console.WriteLine(" With String.Equals:");
Console.WriteLine($" Ordinal: {string.Equals(str1, str2, StringComparison.Ordinal)}");
string ShowBytes(string str)
{
string hexString = string.Empty;
for (int ctr = 0; ctr < str.Length; ctr++)
{
string 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:
// Ordinal: 97
// With String.Equals:
// Ordinal: False
Module Program
Sub Main()
Dim str1 As String = "Aa"
Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"
Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
Console.WriteLine(" With String.Compare:")
Console.WriteLine($" Ordinal: {String.Compare(str1, str2, StringComparison.Ordinal)}")
Console.WriteLine(" With String.Equals:")
Console.WriteLine($" Ordinal: {String.Equals(str1, str2, StringComparison.Ordinal)}")
End Sub
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 = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
hexString &= result
Next
Return hexString.Trim()
End Function
' 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
End Module
대/소문자를 구분하지 않는 서수 비교는 그다음으로 가장 신중한 접근법입니다. 이러한 비교에서는 대부분의 대/소문자를 무시하여, 예를 들어 "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.Ordinal 둘 다 StringComparison.OrdinalIgnoreCase 이진 값을 직접 사용하며 일치에 가장 적합합니다. 비교 설정에 대해 잘 모르는 경우 다음 두 값 중 하나를 사용합니다. 그러나 바이트 바이트 비교를 수행하기 때문에 언어 정렬 순서(예: 영어 사전)를 기준으로 정렬하지 않고 이진 정렬 순서로 정렬합니다. 사용자에게 표시되는 경우 대부분의 컨텍스트에서 결과가 이상하게 보일 수 있습니다.
서수적 의미는 String.Equals 인수를 포함하지 않는 오버로드(같음 연산자를 포함)를 위한 기본값 StringComparison입니다. 어떤 경우든 StringComparison 매개 변수를 포함하는 오버로드를 호출하는 것이 좋습니다.
고정 문화 설정을 사용하는 문자열 연산
정적 CompareInfo 속성에서 반환된 CultureInfo.InvariantCulture 속성을 사용하여 고정 문화권과 비교합니다. 이 동작은 모든 시스템에서 동일합니다. 범위 밖의 모든 문자를 동일한 고정 문자라고 생각되는 문자로 변환합니다. 이 정책은 문화권 간에 하나의 문자열 동작 집합을 유지하는 데 유용할 수 있지만 예기치 않은 결과를 제공하는 경우가 많습니다.
변하지 않는 문화권과 대/소문자를 구분하지 않는 비교는 비교 조건 정보에도 정적 속성에서 반환된 정적 CompareInfoCultureInfo.InvariantCulture 속성을 사용합니다. 이러한 번역된 문자 간의 대/소문자 차이는 무시됩니다.
ASCII 문자열에서 StringComparison.InvariantCulture 및 StringComparison.Ordinal를 사용하는 비교는 동일하게 작동합니다. 그러나 StringComparison.InvariantCulture 바이트 집합으로 해석해야 하는 문자열에 적합하지 않을 수 있는 언어적 결정을 내립니다.
CultureInfo.InvariantCulture.CompareInfo
개체는 Compare 메서드가 특정 문자 집합을 동등한 것으로 해석하도록 합니다. 예를 들어 다음 동등성은 고정 문화권에서 유효합니다.
InvariantCulture: a + = å
LATIN SMALL LETTER A 문자 "a"(\u0061), "+ " "(\u030a) 위의 결합 링 옆에 있는 경우 라틴 문자 A WITH RING ABOVE 문자 "å"(\u00e5)로 해석됩니다. 다음 예제와 같이 이 동작은 서수 비교와 다릅니다.
string separated = "\u0061\u030a";
string combined = "\u00e5";
Console.WriteLine($"Equal sort weight of {separated} and {combined} using InvariantCulture: {string.Compare(separated, combined, StringComparison.InvariantCulture) == 0}");
Console.WriteLine($"Equal sort weight of {separated} and {combined} using Ordinal: {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
Module Program
Sub Main()
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
End Sub
End Module
파일 이름, 쿠키 또는 "å"와 같은 조합이 나타날 수 있는 다른 항목을 해석할 때 서수 비교는 여전히 가장 투명하고 적절한 동작을 제공합니다.
분산 문화권에는 비교에 유용한 속성이 거의 없습니다. 언어적으로 관련된 방식으로 비교를 수행하므로 완전한 기호적 동등성을 보장하지 못하지만 문화권에 표시하기 위한 선택은 아닙니다. 비교에 사용하는 StringComparison.InvariantCulture 몇 가지 이유 중 하나는 문화권 간 동일한 디스플레이에 대해 정렬된 데이터를 유지하는 것입니다. 예를 들어, 애플리케이션과 함께 제공되는 표시용 정렬된 식별자 목록이 포함된 큰 데이터 파일에 항목을 추가하려면, 불변 스타일 정렬을 사용하여 삽입해야 합니다.
메서드 호출에 대한 StringComparison 멤버 선택
의미 문자열 컨텍스트에서 StringComparison 열거형 멤버로의 매핑을 다음 표에 설명합니다.
데이터 | 행동 | 해당하는 문자열 비교 방법 System.StringComparison 가치 |
---|---|---|
대소문자를 구분하는 내부 식별자입니다. XML 및 HTTP와 같은 표준의 대/소문자 구분 식별자. 대/소문자를 구분하는 보안 관련 설정입니다. |
바이트가 정확히 일치하는 비언어적 식별자입니다. | Ordinal |
대/소문자를 구분하지 않는 내부 식별자입니다. XML 및 HTTP와 같은 표준의 대/소문자를 구분하지 않는 식별자입니다. 파일 경로입니다. 레지스트리 키 및 값입니다. 환경 변수입니다. 리소스 식별자(예: 이름 처리). 대/소문자를 구분하지 않는 보안 관련 설정입니다. |
대소문자가 상관없는 비언어적 식별자입니다. | OrdinalIgnoreCase |
일부 지속된, 언어적으로 관련된 데이터입니다. 고정 정렬 순서가 필요한 언어 데이터를 표시합니다. |
여전히 언어적으로 관련 있는 문화적으로 중립적인 데이터입니다. | InvariantCulture -또는- InvariantCultureIgnoreCase |
사용자에게 표시되는 데이터입니다. 대부분의 사용자 입력. |
로컬 언어적 관습이 필요한 데이터입니다. | CurrentCulture -또는- CurrentCultureIgnoreCase |
.NET의 일반적인 문자열 비교 메서드
다음 섹션에서는 문자열 비교에 가장 일반적으로 사용되는 메서드에 대해 설명합니다.
String.Compare
기본 해석: StringComparison.CurrentCulture.
문자열 해석의 가장 중심이 되는 작업인 이러한 메서드 호출의 모든 인스턴스를 검사하여 문자열을 현재 문화권에 따라 해석할지 또는 문화권에서 분리해야 하는지(기호적으로) 결정해야 합니다. 일반적으로 후자가 맞으며, StringComparison.Ordinal 비교를 대신 사용해야 합니다.
System.Globalization.CompareInfo 속성에 의해 반환되는 CultureInfo.CompareInfo 클래스는 플래그 열거형(Compare)을 통해 많은 수의 일치 옵션(서수, 공백 무시, 가나 형식 무시 등)을 제공하는 CompareOptions 메서드를 포함합니다.
String.CompareTo
기본 해석: StringComparison.CurrentCulture.
이 메서드는 현재 StringComparison 유형을 지정하는 오버로드를 제공하지 않습니다. 일반적으로 이 메서드를 권장 String.Compare(String, String, StringComparison) 되는 양식으로 변환할 수 있습니다.
IComparable 및 IComparable<T> 인터페이스를 구현하는 형식이 이 메서드를 구현합니다. 형식이 StringComparison 매개변수 옵션을 제공하지 않기 때문에 구현할 때 생성자에서 사용자가 StringComparer를 지정하도록 하는 경우가 많습니다. 다음 예제에서는 FileName
클래스의 생성자가 StringComparer 매개 변수를 포함하는 클래스를 정의합니다. 이 StringComparer 개체는 FileName.CompareTo
메서드에서 사용됩니다.
class FileName : IComparable
{
private readonly StringComparer _comparer;
public string Name { get; }
public FileName(string name, StringComparer? comparer)
{
if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
Name = name;
if (comparer != null)
_comparer = comparer;
else
_comparer = StringComparer.OrdinalIgnoreCase;
}
public int CompareTo(object? obj)
{
if (obj == null) return 1;
if (obj is not FileName)
return _comparer.Compare(Name, obj.ToString());
else
return _comparer.Compare(Name, ((FileName)obj).Name);
}
}
Class FileName
Implements IComparable
Private ReadOnly _comparer As StringComparer
Public ReadOnly Property Name As String
Public Sub New(name As String, comparer As StringComparer)
If (String.IsNullOrEmpty(name)) Then Throw New ArgumentNullException(NameOf(name))
Me.Name = name
If comparer IsNot Nothing Then
_comparer = comparer
Else
_comparer = StringComparer.OrdinalIgnoreCase
End If
End Sub
Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
If obj Is Nothing Then Return 1
If TypeOf obj IsNot FileName Then
Return _comparer.Compare(Name, obj.ToString())
Else
Return _comparer.Compare(Name, DirectCast(obj, FileName).Name)
End If
End Function
End Class
String.Equals
기본 해석: StringComparison.Ordinal.
이 String 클래스를 사용하면 정적 또는 인스턴스 Equals 메서드 오버로드를 호출하거나 정적 같음 연산자를 사용하여 같음을 테스트할 수 있습니다. 오버로드 및 연산자는 기본적으로 서수 비교를 사용합니다. 그러나 서수 비교를 수행하려는 경우에도 형식을 명시적으로 지정 StringComparison 하는 오버로드를 호출하는 것이 좋습니다. 이렇게 하면 특정 문자열 해석에 대한 코드를 더 쉽게 검색할 수 있습니다.
String.ToUpper 메서드 및 String.ToLower 메서드
기본 해석: StringComparison.CurrentCulture.
문자열을 대문자 또는 소문자로 강제 변환하는 것은 보통 대소문자에 상관없이 문자열을 비교하기 위한 작은 정규화 과정으로 사용되므로, String.ToUpper()와 String.ToLower() 메서드를 사용할 때 주의해야 합니다. 그렇다면 대/소문자를 구분하지 않는 비교를 사용하는 것이 좋습니다.
String.ToUpperInvariant 메서드와 String.ToLowerInvariant 메서드도 사용할 수 있습니다. ToUpperInvariant 는 대/소문자를 정규화하는 표준 방법입니다. StringComparison.OrdinalIgnoreCase를 사용하는 비교는 두 문자열 인수에 대해 ToUpperInvariant를 호출하고 그리고 StringComparison.Ordinal를 사용하여 비교를 수행하는 것으로 되어 있습니다.
오버로드는 해당 문화권을 나타내는 개체를 메서드에 전달 CultureInfo 하여 특정 문화권에서 대문자 및 소문자로 변환할 수도 있습니다.
Char.ToUpper 및 Char.ToLower
기본 해석: StringComparison.CurrentCulture.
Char.ToUpper(Char) 및 Char.ToLower(Char) 메서드는 이전 섹션에서 설명한 String.ToUpper() 및 String.ToLower() 메서드와 유사하게 작동합니다.
String.StartsWith 및 String.EndsWith
기본 해석: StringComparison.CurrentCulture.
기본적으로 이러한 두 메서드는 모두 문화권 구분 비교를 수행합니다. 특히 인쇄할 수 없는 문자를 무시할 수 있습니다.
String.IndexOf 및 String.LastIndexOf
기본 해석: StringComparison.CurrentCulture.
이러한 메서드의 기본 오버로드가 비교를 수행하는 방식에 일관성이 없습니다. 매개 변수를 포함하는 모든 String.IndexOf 및 String.LastIndexOf 메서드는 서수 비교를 수행하지만, 기본 Char 및 String.IndexOf 메서드 중 매개 변수를 포함한 String.LastIndexOf 는 문화권 구분 비교를 수행합니다.
String.IndexOf(String) 또는 String.LastIndexOf(String) 메서드를 호출하여 현재 인스턴스에서 찾을 문자열을 전달하는 경우, 형식을 명시적으로 지정하는 오버로드를 StringComparison를 호출하는 것이 좋습니다. Char 인수가 포함된 오버로드에서는 StringComparison 형식을 지정할 수 없습니다.
간접적으로 문자열 비교를 수행하는 메서드
문자열을 중앙 연산으로 비교하는 일부 비 문자열 메서드는 이 형식을 StringComparer 사용합니다. StringComparer 클래스에는 StringComparer 메서드가 다음 형식의 문자열 비교를 수행하는 인스턴스를 반환하는 6개의 정적 속성이 포함되어 있습니다.
- 현재 문화에 민감한 문화 기반 문자열 비교. 이 StringComparer 개체는 속성에서 반환됩니다 StringComparer.CurrentCulture .
- 현재 문화를 사용하여 대소문자를 구분하지 않는 비교입니다. 이 StringComparer 개체는 속성에서 반환됩니다 StringComparer.CurrentCultureIgnoreCase .
- 비교할 문화권에 구애받지 않고 불변 문화의 단어 비교 규칙을 사용하여 비교합니다. 이 StringComparer 개체는 속성에서 반환됩니다 StringComparer.InvariantCulture .
- 고정 문화의 단어 비교 규칙을 사용하여 대소문자와 문화의 영향을 받지 않는 비교입니다. 이 StringComparer 개체는 속성에서 반환됩니다 StringComparer.InvariantCultureIgnoreCase .
- 서수 비교. 이 StringComparer 개체는 속성에서 반환됩니다 StringComparer.Ordinal .
- 대/소문자를 구분하지 않는 서수 비교입니다. 이 StringComparer 개체는 속성에서 반환됩니다 StringComparer.OrdinalIgnoreCase .
Array.Sort 및 Array.BinarySearch
기본 해석: StringComparison.CurrentCulture.
컬렉션에 데이터를 저장하거나 파일 또는 데이터베이스의 저장된 데이터를 컬렉션으로 읽는 경우에 현재 문화권을 전환하면 컬렉션의 불변 조건이 깨질 수 있습니다. 메서드는 Array.BinarySearch 검색할 배열의 요소가 이미 정렬되어 있다고 가정합니다. 배열의 문자열 요소를 정렬하기 위해 메서드는 Array.Sort 메서드를 String.Compare 호출하여 개별 요소를 정렬합니다. 문화권 구분 비교자를 사용하면 배열이 정렬된 시간과 해당 내용이 검색되는 시간 사이에 문화권이 변경될 경우 위험할 수 있습니다. 예를 들어, 다음 코드에서는 Thread.CurrentThread.CurrentCulture
속성이 암시적으로 제공하는 비교자에 대해 스토리지 및 검색이 수행됩니다. 문화가 StoreNames
와 DoesNameExist
사이의 호출 중에 변경될 수 있으며, 특히 배열 내용이 두 메서드 호출 사이에 어딘가에 유지될 경우 이진 검색이 실패할 수 있습니다.
// Incorrect
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name) >= 0; // Line B
' Incorrect
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name) >= 0 ' Line B
End Function
다음 예제에서는 배열을 정렬하고 검색하는 데 동일한 서수(문화권을 구분하지 않는) 비교 메서드를 사용하는 권장 변형이 나타납니다. 변경 코드는 레이블이 지정된 Line A
줄과 Line B
두 예제에 반영됩니다.
// Correct
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames, StringComparer.Ordinal); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0; // Line B
' Correct
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames, StringComparer.Ordinal) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0 ' Line B
End Function
이 데이터가 문화권 간에 유지 및 이동되고 이 데이터를 사용자에게 표시하는 데 정렬을 사용하는 경우 더 나은 사용자 출력을 위해 언어적으로 작동하지만 문화권의 변경에 영향을 받지 않는 사용을 고려할 StringComparison.InvariantCulture수 있습니다. 다음 예제에서는 배열을 정렬하고 검색하기 위해 고정 문화권을 사용하도록 이전의 두 예제를 수정합니다.
// Correct
string[] _storedNames;
public void StoreNames(string[] names)
{
_storedNames = new string[names.Length];
// Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length);
Array.Sort(_storedNames, StringComparer.InvariantCulture); // Line A
}
public bool DoesNameExist(string name) =>
Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0; // Line B
' Correct
Dim _storedNames As String()
Sub StoreNames(names As String())
ReDim _storedNames(names.Length - 1)
' Copy the array contents into a new array
Array.Copy(names, _storedNames, names.Length)
Array.Sort(_storedNames, StringComparer.InvariantCulture) ' Line A
End Sub
Function DoesNameExist(name As String) As Boolean
Return Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B
End Function
컬렉션 예제: 해시 테이블 생성자
해시 문자열은 문자열을 비교하는 방법에 의해 영향을 받는 작업의 두 번째 예제를 제공합니다.
다음은 Hashtable 속성에서 반환된 StringComparer 개체를 전달하여 StringComparer.OrdinalIgnoreCase 개체를 인스턴스화하는 예제입니다. 파생된 StringComparer 클래스 StringComparer 가 인터페이스를 IEqualityComparer 구현하기 때문에 해당 GetHashCode 메서드는 해시 테이블의 문자열 해시 코드를 계산하는 데 사용됩니다.
using System.IO;
using System.Collections;
const int InitialCapacity = 100;
Hashtable creationTimeByFile = new(InitialCapacity, StringComparer.OrdinalIgnoreCase);
string directoryToProcess = Directory.GetCurrentDirectory();
// Fill the hash table
PopulateFileTable(directoryToProcess);
// Get some of the files and try to find them with upper cased names
foreach (var file in Directory.GetFiles(directoryToProcess))
PrintCreationTime(file.ToUpper());
void PopulateFileTable(string directory)
{
foreach (string file in Directory.GetFiles(directory))
creationTimeByFile.Add(file, File.GetCreationTime(file));
}
void PrintCreationTime(string targetFile)
{
object? dt = creationTimeByFile[targetFile];
if (dt is DateTime value)
Console.WriteLine($"File {targetFile} was created at time {value}.");
else
Console.WriteLine($"File {targetFile} does not exist.");
}
Imports System.IO
Module Program
Const InitialCapacity As Integer = 100
Private ReadOnly s_creationTimeByFile As New Hashtable(InitialCapacity, StringComparer.OrdinalIgnoreCase)
Private ReadOnly s_directoryToProcess As String = Directory.GetCurrentDirectory()
Sub Main()
' Fill the hash table
PopulateFileTable(s_directoryToProcess)
' Get some of the files and try to find them with upper cased names
For Each File As String In Directory.GetFiles(s_directoryToProcess)
PrintCreationTime(File.ToUpper())
Next
End Sub
Sub PopulateFileTable(directoryPath As String)
For Each file As String In Directory.GetFiles(directoryPath)
s_creationTimeByFile.Add(file, IO.File.GetCreationTime(file))
Next
End Sub
Sub PrintCreationTime(targetFile As String)
Dim dt As Object = s_creationTimeByFile(targetFile)
If TypeOf dt Is Date Then
Console.WriteLine($"File {targetFile} was created at time {DirectCast(dt, Date)}.")
Else
Console.WriteLine($"File {targetFile} does not exist.")
End If
End Sub
End Module
참고하십시오
.NET