Praktik terbaik untuk membandingkan string di .NET
.NET menyediakan dukungan ekstensif untuk mengembangkan aplikasi lokal dan global, serta memudahkan penerapan konvensi budaya saat ini atau budaya tertentu saat melakukan operasi umum seperti mengurutkan dan menampilkan string. Tetapi mengurutkan atau membandingkan string tidak selalu merupakan operasi yang sensitif terhadap budaya. Misalnya, string yang digunakan secara internal oleh aplikasi biasanya harus ditangani secara identik di semua budaya. Ketika data string independen secara budaya, seperti tag XML, tag HTML, nama pengguna, jalur file, dan nama objek sistem, ditafsirkan seolah-olah mereka peka budaya, kode aplikasi dapat tunduk pada bug halus, performa yang buruk, dan, dalam beberapa kasus, masalah keamanan.
Artikel ini memeriksa metode pengurutan, perbandingan, dan casing string di .NET, menyajikan rekomendasi untuk memilih metode penanganan string yang sesuai, dan memberikan informasi tambahan tentang metode penanganan string.
Rekomendasi untuk penggunaan string
Saat Anda mengembangkan dengan .NET, ikuti rekomendasi ini saat Anda membandingkan string.
Tip
Berbagai metode terkait string melakukan perbandingan. Contohnya termasuk String.Equals, , String.IndexOfString.Compare, dan String.StartsWith.
- Gunakan overload yang secara eksplisit menentukan aturan perbandingan string untuk operasi string. Biasanya, ini melibatkan pemanggilan metode overload yang memiliki parameter jenis StringComparison.
- Gunakan StringComparison.Ordinal atau StringComparison.OrdinalIgnoreCase untuk perbandingan sebagai default aman Anda untuk pencocokan string budaya agnostik.
- Gunakan perbandingan dengan StringComparison.Ordinal atau StringComparison.OrdinalIgnoreCase untuk performa yang lebih baik.
- Gunakan operasi string yang didasarkan pada StringComparison.CurrentCulture saat Anda menampilkan output kepada pengguna.
- Gunakan nilai StringComparison.Ordinal atau StringComparison.OrdinalIgnoreCase non-linguistik, bukan operasi string berdasarkan CultureInfo.InvariantCulture ketika perbandingannya tidak relevan secara linguistik (simbolis, misalnya).
- Gunakan metode String.ToUpperInvariant, bukan metode String.ToLowerInvariant saat Anda menormalkan string untuk perbandingan.
- Gunakan overload metode String.Equals untuk menguji apakah dua string sama.
- Gunakan metode String.Compare dan String.CompareTo untuk mengurutkan string, bukan untuk memeriksa kesetaraan.
- Gunakan pemformatan yang sensitif terhadap budaya untuk menampilkan data non-string, seperti angka dan tanggal, di antarmuka pengguna. Gunakan pemformatan dengan kultur invarian untuk mempertahankan data non-string dalam bentuk string.
Hindari praktik berikut saat Anda membandingkan string:
- Jangan gunakan kelebihan beban yang tidak secara eksplisit atau implisit menentukan aturan perbandingan string untuk operasi string.
- Jangan gunakan operasi string berdasarkan StringComparison.InvariantCulture dalam kebanyakan kasus. Salah satu dari beberapa pengecualian adalah ketika Anda mempertahankan data agnostik linguistik yang bermakna tetapi secara budaya.
- Jangan gunakan kelebihan beban String.Compare metode atau CompareTo dan uji nilai pengembalian nol untuk menentukan apakah dua string sama.
Menentukan perbandingan string secara eksplisit
Sebagian besar metode manipulasi string di .NET adalah overload. Biasanya, satu atau beberapa overload menerima pengaturan default, sedangkan yang lain tidak menerima default dan sebaliknya menentukan cara yang tepat di mana string akan dibandingkan atau dimanipulasi. Sebagian besar metode yang tidak bergantung pada default mencakup parameter jenis StringComparison, yang merupakan enumerasi yang secara eksplisit menentukan aturan untuk perbandingan string berdasarkan budaya dan kasus. Tabel berikut ini menjelaskan anggota enumerasi StringComparison.
Anggota StringComparison | Deskripsi |
---|---|
CurrentCulture | Melakukan perbandingan peka huruf besar/kecil menggunakan budaya saat ini. |
CurrentCultureIgnoreCase | Melakukan perbandingan tidak peka huruf besar/kecil menggunakan budaya saat ini. |
InvariantCulture | Melakukan perbandingan peka huruf besar/kecil menggunakan kultur invarian. |
InvariantCultureIgnoreCase | Melakukan perbandingan tidak peka huruf besar/kecil menggunakan kultur invarian. |
Ordinal | Melakukan perbandingan ordinal. |
OrdinalIgnoreCase | Melakukan perbandingan ordinal yang tidak peka huruf besar/kecil. |
Misalnya, metode IndexOf, yang mengembalikan indeks substring dalam objek String yang cocok dengan karakter atau string, memiliki sembilan overload:
- IndexOf(Char), , IndexOf(Char, Int32)dan IndexOf(Char, Int32, Int32), yang secara default melakukan pencarian ordinal (peka huruf besar/kecil dan tidak peka terhadap budaya) untuk karakter dalam string.
- IndexOf(String), , IndexOf(String, Int32)dan IndexOf(String, Int32, Int32), yang secara default melakukan pencarian peka huruf besar/kecil dan sensitif terhadap budaya untuk substring dalam string.
- IndexOf(String, StringComparison), IndexOf(String, Int32, StringComparison), and IndexOf(String, Int32, Int32, StringComparison), yang menyertakan parameter jenis StringComparison yang memungkinkan bentuk perbandingan ditentukan.
Kami menyarankan agar Anda memilih kelebihan beban yang tidak menggunakan nilai default, karena alasan berikut:
Beberapa overload dengan parameter default (yang mencari Char dalam instans string) melakukan perbandingan ordinal, sedangkan yang lain (yang mencari string dalam instans string) peka terhadap budaya. Sulit untuk mengingat metode mana yang menggunakan nilai default mana, dan mudah membingungkan kelebihan beban.
Niat kode yang bergantung pada nilai default untuk panggilan metode tidak jelas. Dalam contoh berikut, yang bergantung pada default, sulit untuk mengetahui apakah pengembang benar-benar dimaksudkan perbandingan ordinal atau linguistik dari dua string, atau apakah perbedaan kasus antara
url.Scheme
dan "https" dapat menyebabkan pengujian agar kesetaraan kembalifalse
.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
Secara umum, kami sarankan Anda memanggil metode yang tidak bergantung pada default, karena membuat niat kode tidak ambigu. Pada gilirannya, ini membuat kode lebih mudah dibaca dan lebih mudah untuk di-debug serta dirawat. Contoh berikut membahas pertanyaan yang diajukan tentang contoh sebelumnya. Ini memperjelas bahwa perbandingan ordinal digunakan dan perbedaan jika diabaikan.
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
Detail perbandingan string
Perbandingan string adalah inti dari banyak operasi terkait string, terutama pengurutan dan pengujian untuk kesetaraan. String mengurutkan dalam urutan yang ditentukan: Jika "my" muncul sebelum "string" dalam daftar string yang diurutkan, "my" harus membandingkan kurang dari atau sama dengan "string". Selain itu, perbandingan secara implisit mendefinisikan kesetaraan. Operasi perbandingan mengembalikan nol untuk string yang dianggap sama. Interpretasi yang baik adalah bahwa tidak ada string yang kurang dari yang lain. Sebagian besar operasi yang bermakna yang melibatkan string mencakup satu atau kedua prosedur ini: membandingkan dengan string lain, dan menjalankan operasi pengurutan yang ditentukan dengan baik.
Catatan
Anda dapat mengunduh Tabel Bobot Pengurutan, sekumpulan file teks yang berisi informasi tentang bobot karakter yang digunakan dalam operasi pengurutan dan perbandingan untuk sistem operasi Windows, dan Tabel Elemen Kolase Unicode Default, versi terbaru tabel bobot sortir untuk Linux dan macOS. Versi spesifik tabel bobot sortir di Linux dan macOS tergantung pada versi pustaka Komponen Internasional untuk Unicode yang diinstal pada sistem. Untuk informasi tentang versi ICU dan versi Unicode yang diterapkan, lihat Mengunduh ICU.
Namun, mengevaluasi dua string untuk kesetaraan atau urutan pengurutan tidak menghasilkan satu hasil yang benar; hasilnya tergantung pada kriteria yang digunakan untuk membandingkan string. Secara khusus, perbandingan string yang bersifat ordinal atau yang didasarkan konvensi casing dan penyortiran budaya saat ini atau kultur invarian (budaya lokal agnostik berdasarkan bahasa Inggris) dapat menghasilkan hasil yang berbeda.
Selain itu, perbandingan string yang menggunakan versi .NET berbeda atau menggunakan .NET pada sistem operasi atau versi sistem operasi yang berbeda dapat mengembalikan hasil yang berbeda. Untuk informasi lebih lanjut, lihat String dan Standar Unicode.
Perbandingan string yang menggunakan budaya saat ini
Salah satu kriteria melibatkan penggunaan konvensi budaya saat ini saat membandingkan string. Perbandingan yang didasarkan pada budaya saat ini menggunakan budaya atau lokal utas saat ini. Jika budaya tidak diatur oleh pengguna, budaya tersebut default ke pengaturan sistem operasi. Anda harus selalu menggunakan perbandingan yang didasarkan pada budaya saat ini ketika data relevan secara linguistik, dan ketika mencerminkan interaksi pengguna yang sensitif terhadap budaya.
Namun, perilaku perbandingan dan casing di .NET berubah ketika budaya berubah. Ini terjadi ketika aplikasi dijalankan di komputer yang memiliki budaya yang berbeda dari komputer tempat aplikasi dikembangkan, atau ketika utas yang dieksekusi mengubah budayanya. Perilaku ini disengaja, tetapi tetap tidak jelas untuk banyak pengembang. Contoh berikut menggambarkan perbedaan urutan sortir antara budaya Inggris AS ("en-US") dan Swedia ("sv-SE"). Perhatikan bahwa kata "ångström", "Windows", dan "Visual Studio" muncul di posisi yang berbeda dalam array string yang diurutkan.
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
Perbandingan tidak peka huruf besar/kecil yang menggunakan budaya saat ini sama dengan perbandingan sensitif budaya, kecuali bahwa mereka mengabaikan kasus seperti yang ditentukan oleh budaya utas saat ini. Perilaku ini juga dapat memanifestasikan dirinya dalam urutan sortir.
Perbandingan yang menggunakan semantik budaya saat ini adalah default untuk metode berikut:
- String.Compare kelebihan beban yang tidak menyertakan StringComparison parameter.
- String.CompareTo overload.
- Metode default String.StartsWith(String), dan metode String.StartsWith(String, Boolean, CultureInfo) dengan parameter
null
CultureInfo. - Metode default String.EndsWith(String), dan metode String.EndsWith(String, Boolean, CultureInfo) dengan parameter
null
CultureInfo. - String.IndexOf kelebihan beban yang menerima String sebagai parameter pencarian dan yang tidak memiliki StringComparison parameter.
- String.LastIndexOf kelebihan beban yang menerima String sebagai parameter pencarian dan yang tidak memiliki StringComparison parameter.
Bagaimana pun, sebaiknya panggil overload yang memiliki parameter StringComparison untuk membuat niat panggilan metode jelas.
Bug halus dan tidak begitu halus dapat muncul ketika data string non-linguistik ditafsirkan secara linguistik, atau ketika data string dari budaya tertentu ditafsirkan menggunakan konvensi budaya lain. Contoh kanonis adalah masalah I Turki.
Untuk hampir semua alfabet Latin, termasuk bahasa Inggris AS, karakter "i" (\u0069) adalah versi huruf kecil dari karakter "I" (\u0049). Aturan kapital ini dengan cepat menjadi default bagi seseorang yang memprogram dalam budaya seperti itu. Namun, alfabet Turki ("tr-TR") menyertakan karakter "I dengan titik" "İ" (\u0130), yang merupakan versi modal "i". Bahasa Turki juga menyertakan huruf kecil "i tanpa titik", "ı" (\u0131), yang menggunakan huruf kapital "I". Perilaku ini juga terjadi pada budaya Azerbaijan ("az").
Oleh karena itu, asumsi yang dibuat tentang memanfaatkan "i" atau huruf kecil "I" tidak berlaku di antara semua budaya. Jika Anda menggunakan overload default untuk rutinitas perbandingan string, overload akan tunduk pada varians antara budaya. Jika data yang akan dibandingkan adalah non-linguistik, menggunakan kelebihan beban default dapat menghasilkan hasil yang tidak diinginkan, karena upaya berikut untuk melakukan perbandingan yang tidak peka huruf besar/kecil dari string "bill" dan "BILL" menggambarkan.
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
Perbandingan ini dapat menyebabkan masalah signifikan jika budaya secara tidak sengaja digunakan dalam pengaturan sensitif keamanan, seperti dalam contoh berikut. Panggilan metode seperti IsFileURI("file:")
mengembalikan true
jika budaya saat ini adalah Bahasa Inggris AS, tetapi false
jika budaya saat ini adalah Turki. Dengan demikian, pada sistem Turki, seseorang dapat menghindari langkah-langkah keamanan yang memblokir akses ke URI yang tidak peka huruf besar/kecil yang dimulai dengan "FILE:".
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
Dalam hal ini, karena "file:" dimaksudkan untuk ditafsirkan sebagai pengidentifikasi non-linguistik, tidak peka budaya, kode seharusnya ditulis seperti yang ditunjukkan dalam contoh berikut:
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
Operasi string ordinal
Menentukan nilai StringComparison.Ordinal atau StringComparison.OrdinalIgnoreCase dalam pemanggilan metode menandakan perbandingan non-linguistik di mana fitur bahasa alami diabaikan. Metode yang dipanggil dengan nilai StringComparison ini mendasarkan keputusan operasi string pada perbandingan byte sederhana, bukan tabel casing atau ekuivalensi yang diparameterisasi oleh budaya. Dalam kebanyakan kasus, pendekatan ini paling sesuai dengan interpretasi string yang dimaksudkan sambil membuat kode lebih cepat dan lebih andal.
Perbandingan ordinal adalah perbandingan string di mana setiap byte dari setiap string dibandingkan tanpa interpretasi linguistik; misalnya, "windows" tidak cocok dengan "Windows". Ini pada dasarnya adalah panggilan ke fungsi strcmp
runtime C. Gunakan perbandingan ini ketika konteks menentukan bahwa string harus dicocokkan dengan tepat atau menuntut kebijakan pencocokan konservatif. Selain itu, perbandingan ordinal adalah operasi perbandingan tercepat karena tidak menerapkan aturan linguistik saat menentukan hasil.
String dalam .NET dapat berisi karakter null yang disematkan (dan karakter non-pencetakan lainnya). Salah satu perbedaan paling jelas antara perbandingan ordinal dan sensitif budaya (termasuk perbandingan yang menggunakan kultur invarian) menyangkut penanganan karakter null yang disematkan dalam string. Karakter ini diabaikan saat Anda menggunakan metode String.Compare dan String.Equals untuk melakukan perbandingan peka budaya (termasuk perbandingan yang menggunakan kultur invarian). Akibatnya, string yang berisi karakter null yang disematkan dapat dianggap sama dengan string yang tidak. Karakter non-pencetakan yang disematkan mungkin dilewati untuk tujuan metode perbandingan string, seperti String.StartsWith.
Penting
Meskipun metode perbandingan string mengabaikan karakter null yang disematkan, metode pencarian string seperti String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf, dan String.StartsWith tidak.
Contoh berikut melakukan perbandingan sensitif budaya dari string "Aa" dengan string serupa yang berisi beberapa karakter null yang disematkan antara "A" dan "a", dan menunjukkan bagaimana kedua string dianggap sama:
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
Namun, string tidak dianggap sama saat Anda menggunakan perbandingan ordinal, seperti yang ditunjukkan contoh berikut:
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
Perbandingan ordinal yang tidak peka huruf besar/kecil adalah pendekatan paling konservatif berikutnya. Perbandingan ini mengabaikan sebagian besar casing; misalnya, "windows" cocok dengan "Windows". Saat berhadapan dengan karakter ASCII, kebijakan ini setara dengan StringComparison.Ordinal, kecuali bahwa kebijakan ini mengabaikan casing ASCII yang biasa. Oleh karena itu, karakter apa pun di [A, Z] (\u0041-\u005A) cocok dengan karakter yang sesuai di [a,z] (\u0061-\007A). Casing di luar rentang ASCII menggunakan tabel kultur invarian. Oleh karena itu, perbandingan berikut:
string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)
setara dengan (tetapi lebih cepat dari) perbandingan ini:
string.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal)
Perbandingan ini masih sangat cepat.
Baik StringComparison.Ordinal dan StringComparison.OrdinalIgnoreCase menggunakan nilai biner secara langsung, dan paling cocok untuk pencocokan. Saat Anda tidak yakin tentang pengaturan perbandingan Anda, gunakan salah satu dari dua nilai ini. Namun, karena mereka melakukan perbandingan byte-byte, mereka tidak mengurutkan berdasarkan urutan sortir linguistik (seperti kamus bahasa Inggris) tetapi dengan urutan pengurutan biner. Hasilnya mungkin terlihat aneh di sebagian besar konteks jika ditampilkan kepada pengguna.
Semantik ordinal adalah default untuk String.Equals kelebihan beban yang tidak menyertakan StringComparison argumen (termasuk operator kesetaraan). Bagaimanapun, sebaiknya panggil overload yang memiliki parameter StringComparison.
Operasi string yang menggunakan kultur invarian
Perbandingan dengan kultur invarian menggunakan properti CompareInfo yang dikembalikan oleh properti CultureInfo.InvariantCulture statik. Perilaku ini sama pada semua sistem; ini menerjemahkan karakter apa pun di luar rentangnya ke dalam apa yang diyakininya adalah karakter invarian yang setara. Kebijakan ini dapat berguna untuk mempertahankan satu set perilaku string di seluruh budaya, tetapi sering memberikan hasil yang tidak terduga.
Perbandingan yang tidak peka huruf besar/kecil dengan kultur invarian menggunakan properti CompareInfo statik yang dikembalikan oleh properti CultureInfo.InvariantCulture statik untuk informasi perbandingan juga. Perbedaan kasus apa pun di antara karakter yang diterjemahkan ini diabaikan.
Perbandingan yang menggunakan StringComparison.InvariantCulture dan StringComparison.Ordinal bekerja secara identik pada string ASCII. Namun, StringComparison.InvariantCulture membuat keputusan linguistik yang mungkin tidak sesuai untuk string yang harus ditafsirkan sebagai satu set byte. Objek CultureInfo.InvariantCulture.CompareInfo
membuat metode Compare menginterpretasikan set karakter tertentu yang setara. Misalnya, kesetaraan berikut berlaku di bawah kultur invarian:
InvariantCulture: a + ̊ = å
HURUF KECIL LATIN Karakter "a" (\u0061), ketika berada di samping karakter COMBINING RING ABOVE "+ " ̊" (\u030a), ditafsirkan sebagai HURUF KECIL LATIN A DENGAN karakter RING DI ATAS "å" (\u00e5). Seperti yang ditunjukkan oleh contoh berikut, perilaku ini berbeda dari perbandingan ordinal.
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
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
Saat menafsirkan nama file, cookie, atau apa pun di mana kombinasi seperti "å" dapat muncul, perbandingan ordinal masih menawarkan perilaku yang paling transparan dan sesuai.
Pada keseimbangan, budaya invarian memiliki beberapa properti yang membuatnya berguna untuk perbandingan. Ini memang perbandingan dengan cara yang relevan secara linguistik, yang mencegahnya menjamin kesetaraan simbolis penuh, tetapi itu bukan pilihan untuk ditampilkan dalam budaya apa pun. Salah satu dari beberapa alasan untuk menggunakan StringComparison.InvariantCulture sebagai perbandingan adalah mempertahankan data yang diurutkan untuk tampilan identik lintas budaya. Misalnya, jika file data besar yang berisi daftar pengidentifikasi yang diurutkan untuk tampilan menyertai aplikasi, menambahkan ke daftar ini akan memerlukan penyisipan dengan pengurutan gaya invarian.
Memilih anggota StringComparison untuk panggilan metode Anda
Tabel berikut menguraikan pemetaan dari konteks string semantik ke anggota enumerasi StringComparison:
Data | Perilaku | System.StringComparison yang sesuai value |
---|---|---|
Pengidentifikasi internal peka huruf besar/kecil. Pengidentifikasi peka huruf besar/kecil dalam standar seperti XML dan HTTP. Pengaturan terkait keamanan peka huruf besar/kecil. |
Pengidentifikasi non-linguistik, di mana byte sama persis. | Ordinal |
Pengidentifikasi internal tidak peka huruf besar/kecil. Pengidentifikasi tidak peka huruf besar/kecil dalam standar seperti XML dan HTTP. Jalur file. Kunci dan nilai registri. Variabel lingkungan. Pengidentifikasi sumber daya (misalnya, nama handel). Pengaturan terkait keamanan tidak peka huruf besar/kecil. |
Pengidentifikasi non-linguistik, di mana kasus tidak relevan. | OrdinalIgnoreCase |
Beberapa data yang bertahan dan relevan secara linguistik. Tampilan data linguistik yang memerlukan urutan sortir tetap. |
Data agnostik yang secara budaya yang masih relevan secara linguistik. | InvariantCulture -atau- InvariantCultureIgnoreCase |
Data ditampilkan kepada pengguna. Sebagian besar input pengguna. |
Data yang memerlukan kustom linguistik lokal. | CurrentCulture -atau- CurrentCultureIgnoreCase |
Metode perbandingan string umum di .NET
Bagian berikut menjelaskan metode yang paling umum digunakan untuk perbandingan string.
String.Compare
Interpretasi default: StringComparison.CurrentCulture.
Sebagai operasi yang paling sentral untuk interpretasi string, semua instans panggilan metode ini harus diperiksa untuk menentukan apakah string harus ditafsirkan sesuai dengan budaya saat ini, atau dipisahkan dari budaya (secara simbolis). Biasanya, ini yang terakhir, dan perbandingan StringComparison.Ordinal harus digunakan sebagai gantinya.
Kelas System.Globalization.CompareInfo, yang dikembalikan oleh properti CultureInfo.CompareInfo, juga mencakup metode Compare yang menyediakan sejumlah besar opsi pencocokan (ordinal, mengabaikan spasi putih, mengabaikan jenis kana, dan sebagainya) dengan cara enumerasi bendera CompareOptions.
String.CompareTo
Interpretasi default: StringComparison.CurrentCulture.
Metode ini saat ini tidak menawarkan kelebihan beban yang menentukan StringComparison jenis. Biasanya dimungkinkan untuk mengonversi metode ini ke formulir yang direkomendasikan String.Compare(String, String, StringComparison) .
Jenis yang mengimplementasikan antarmuka IComparable dan IComparable<T> mengimplementasikan metode ini. Karena tidak menawarkan opsi StringComparison parameter, jenis penerapan sering kali memungkinkan pengguna menentukan StringComparer di konstruktor mereka. Contoh berikut mendefinisikan kelasFileName
yang konstruktor kelasnya menyertakan parameter StringComparer. Objek StringComparer ini kemudian digunakan dalam metode 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
Interpretasi default: StringComparison.Ordinal.
Kelas String ini memungkinkan Anda menguji kesetaraan dengan memanggil overload metode Equals statik atau instans, atau dengan menggunakan operator kesetaraan statis. Overload dan operator menggunakan perbandingan ordinal secara default. Namun, kami masih menyarankan agar Anda memanggil overload yang secara eksplisit menentukan jenis StringComparison bahkan jika Anda ingin melakukan perbandingan ordinal; ini membuatnya lebih mudah untuk mencari kode untuk interpretasi string tertentu.
String.ToUpper and String.ToLower
Interpretasi default: StringComparison.CurrentCulture.
Berhati-hatilah saat Anda menggunakan String.ToUpper() metode dan String.ToLower() , karena memaksa string ke huruf besar atau huruf kecil sering digunakan sebagai normalisasi kecil untuk membandingkan string terlepas dari kasus. Jika demikian, pertimbangkan untuk menggunakan perbandingan yang tidak peka huruf besar/kecil.
Metode String.ToUpperInvariant dan String.ToLowerInvariant juga tersedia. ToUpperInvariant adalah cara standar untuk menormalkan kasus. Perbandingan yang dibuat menggunakan StringComparison.OrdinalIgnoreCase secara perilaku adalah komposisi dua panggilan: memanggil ToUpperInvariant pada kedua argumen string, dan melakukan perbandingan menggunakan StringComparison.Ordinal.
Overload juga tersedia untuk mengonversi ke huruf besar dan huruf kecil dalam budaya tertentu, dengan meneruskan objek CultureInfo yang mewakili budaya tersebut ke metode.
Char.ToUpper and Char.ToLower
Interpretasi default: StringComparison.CurrentCulture.
Metode Char.ToUpper(Char) dan Char.ToLower(Char) bekerja mirip dengan metode String.ToUpper() dan String.ToLower() yang dijelaskan di bagian sebelumnya.
String.StartsWith and String.EndsWith
Interpretasi default: StringComparison.CurrentCulture.
Secara default, kedua metode ini melakukan perbandingan yang sensitif terhadap budaya. Secara khusus, mereka dapat mengabaikan karakter non-pencetakan.
String.IndexOf and String.LastIndexOf
Interpretasi default: StringComparison.CurrentCulture.
Ada kurangnya konsistensi dalam bagaimana kelebihan beban default metode ini melakukan perbandingan. Semua metode String.IndexOf dan String.LastIndexOf yang menyertakan parameter Char melakukan perbandingan ordinal, tetapi metode String.IndexOf dan String.LastIndexOf default yang menyertakan parameter String melakukan perbandingan peka budaya.
Jika Anda memanggil metode String.IndexOf(String) atau String.LastIndexOf(String) dan meneruskannya sebagai string untuk ditemukan dalam instans saat ini, sebaiknya panggil overload yang secara eksplisit menentukan jenis StringComparison. Kelebihan beban yang menyertakan Char argumen tidak memungkinkan Anda menentukan StringComparison jenis.
Metode yang melakukan perbandingan string secara tidak langsung
Beberapa metode non-string yang memiliki perbandingan string sebagai operasi pusat menggunakan jenis StringComparer. Kelas StringComparer mencakup enam properti statik yang mengembalikan instans StringComparer yang metode StringComparer.Compare-nya melakukan jenis perbandingan string berikut:
- Perbandingan string yang sensitif terhadap budaya menggunakan budaya saat ini. Objek StringComparer ini dikembalikan oleh properti StringComparer.CurrentCulture.
- Perbandingan string yang sensitif terhadap budaya menggunakan budaya saat ini. Objek StringComparer ini dikembalikan oleh properti StringComparer.CurrentCultureIgnoreCase.
- Perbandingan yang tidak peka terhadap budaya menggunakan aturan perbandingan kata dari kultur invarian. Objek StringComparer ini dikembalikan oleh properti StringComparer.InvariantCulture.
- Perbandingan yang peka huruf besa/kecil dan peka terhadap budaya menggunakan aturan perbandingan kata dari kultur invarian. Objek StringComparer ini dikembalikan oleh properti StringComparer.InvariantCultureIgnoreCase.
- Perbandingan ordinal. Objek StringComparer ini dikembalikan oleh properti StringComparer.Ordinal.
- Perbandingan ordinal tidak peka huruf besar/kecil. Objek StringComparer ini dikembalikan oleh properti StringComparer.OrdinalIgnoreCase.
Array.Sort dan Array.BinarySearch
Interpretasi default: StringComparison.CurrentCulture.
Saat Anda menyimpan data apa pun dalam koleksi, atau membaca data tersimpan dari file atau database ke dalam koleksi, mengalihkan budaya saat ini dapat membatalkan invarian dalam koleksi. Metode Array.BinarySearch ini mengasumsikan bahwa elemen dalam array yang akan dicari sudah diurutkan. Untuk mengurutkan elemen string apa pun dalam array, metode Array.Sort memanggil metode String.Compare untuk mengurutkan elemen individual. Menggunakan perbandingan sensitif budaya bisa berbahaya jika budaya berubah antara waktu array diurutkan dan kontennya dicari. Misalnya, dalam kode berikut, penyimpanan dan pengambilan beroperasi pada pembanding yang disediakan secara implisit oleh properti Thread.CurrentThread.CurrentCulture
. Jika budaya dapat berubah antara panggilan ke StoreNames
dan DoesNameExist
, dan terutama jika konten array dipertahankan di suatu tempat di antara dua panggilan metode, pencarian biner mungkin gagal.
// 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
Variasi yang direkomendasikan muncul dalam contoh berikut, yang menggunakan metode perbandingan ordinal (tidak peka budaya) yang sama baik untuk mengurutkan maupun untuk mencari array. Kode perubahan tercermin dalam baris berlabel Line A
dan Line B
dalam dua contoh.
// 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
Jika data ini dipertahankan dan dipindahkan lintas budaya, serta pengurutan digunakan untuk menyajikan data ini kepada pengguna, Anda dapat mempertimbangkan untuk menggunakan StringComparison.InvariantCulture, yang beroperasi secara linguistik untuk output pengguna yang lebih baik, tetapi tidak terpengaruh oleh perubahan budaya. Contoh berikut memodifikasi dua contoh sebelumnya untuk menggunakan kultur invarian untuk mengurutkan dan mencari array.
// 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
Contoh koleksi: Konstruktor hashtable
Hashing string memberikan contoh kedua dari operasi yang dipengaruhi oleh cara string dibandingkan.
Contoh berikut membuat instans objek Hashtable dengan meneruskannya ke objek StringComparer yang dikembalikan oleh properti StringComparer.OrdinalIgnoreCase. Karena kelas StringComparer yang diturunkan dari StringComparer mengimplementasikan antarmuka IEqualityComparer, metode GetHashCode-nya digunakan untuk menghitung kode hash string dalam tabel hash.
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