Bagikan melalui


Praktik terbaik untuk membandingkan string di .NET

.NET memberikan dukungan ekstensif untuk mengembangkan aplikasi yang dilokalkan dan di globalisasi, dan 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 terhadap budaya, kode aplikasi dapat tunduk pada bug halus, performa yang buruk, dan, dalam beberapa kasus, masalah keamanan.

Artikel ini memeriksa metode pengurutan string, perbandingan, dan casing 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.

Petunjuk / Saran

Berbagai metode terkait string melakukan perbandingan. Contohnya termasuk String.Equals, , String.CompareString.IndexOf, dan String.StartsWith.

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 menyimpan data yang bermakna secara linguistik tetapi netral secara budaya.
  • Jangan gunakan kelebihan beban String.Compare metode atau CompareTo dan uji nilai pengembalian nol untuk menentukan apakah dua string sama.

Petunjuk / Saran

Aturan analisis kode CA1307, CA1309, dan CA1310 membantu mengidentifikasi situs panggilan di mana perbandingan linguistik digunakan secara tidak sengaja. Untuk mengaktifkannya dan menampilkan pelanggaran sebagai kesalahan build, atur properti berikut dalam file proyek Anda:

<PropertyGroup>
  <AnalysisMode>All</AnalysisMode>
  <WarningsAsErrors>$(WarningsAsErrors);CA1307;CA1309;CA1310</WarningsAsErrors>
</PropertyGroup>

Tentukan perbandingan string secara eksplisit

Sebagian besar metode manipulasi string di .NET kelebihan beban. Biasanya, satu atau beberapa overload menerima pengaturan default, sedangkan yang lain tidak menerima pengaturan default dan menentukan cara tepat untuk membandingkan atau memanipulasi string. 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 StringComparison anggota-anggota enumerasi.

StringComparison anggota Deskripsi
CurrentCulture Melakukan perbandingan peka huruf besar/kecil menggunakan budaya saat ini.
CurrentCultureIgnoreCase Melakukan perbandingan tanpa membedakan huruf besar/kecil menggunakan kultur saat ini.
InvariantCulture Melakukan perbandingan peka huruf besar/kecil menggunakan kultur invarian.
InvariantCultureIgnoreCase Melakukan perbandingan yang tidak peka terhadap 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 dari substring dalam objek String yang cocok dengan karakter atau string, memiliki sembilan overload.

Kami menyarankan agar Anda memilih kelebihan beban yang tidak menggunakan nilai default, karena alasan berikut:

  • Beberapa overload dengan parameter default (yang mencari Char dalam instance string) melakukan perbandingan ordinal, sedangkan yang lain (yang mencari string dalam instance 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 bermaksud melakukan perbandingan ordinal atau linguistik dari dua string, atau apakah perbedaan kasus antara url.Scheme dan "https" dapat menyebabkan pengujian kesamaan mengembalikan 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
    

Secara umum, kami sarankan Anda memanggil metode yang tidak bergantung pada default, karena membuat niat kode tidak ambigu. Ini, pada gilirannya, membuat kode lebih mudah dibaca dan lebih mudah untuk di-debug dan dikelola. Contoh berikut membahas pertanyaan yang diajukan tentang contoh sebelumnya. Ini memperjelas bahwa perbandingan ordinal digunakan dan bahwa 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

Rincian perbandingan string

Perbandingan string adalah inti dari banyak operasi terkait string, terutama pengurutan dan pengujian untuk kesetaraan. String diurutkan dalam urutan yang ditentukan: Jika "my" muncul sebelum "string" dalam daftar string yang diurutkan, "my" harus dibandingkan lebih kecil atau sama dengan "string". Selain itu, perbandingan secara implisit mendefinisikan kesetaraan. Operasi perbandingan mengembalikan nol untuk string yang dianggap sama. Penafsiran yang baik adalah bahwa tidak ada string yang lebih rendah dari yang lainnya. 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.

Nota

Anda dapat mengunduh Tabel Berat 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 berat sortir untuk Linux dan macOS. Versi spesifik tabel berat sortir di Linux dan macOS bergantung pada versi pustaka Komponen Internasional Unicode yang diinstal pada sistem. Untuk informasi tentang versi ICU dan versi Unicode yang mereka terapkan, 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 ordinal atau yang didasarkan pada penyusunan dan pengurutan budaya saat ini atau budaya invarian (budaya yang tidak terpaku pada lokal tertentu, tidak berdasarkan bahasa Inggris) dapat menghasilkan hasil yang berbeda.

Selain itu, perbandingan string menggunakan versi .NET yang berbeda atau menggunakan .NET pada sistem operasi atau versi sistem operasi yang berbeda dapat mengembalikan hasil yang berbeda. .NET menggunakan pustaka Komponen Internasional untuk Unicode (ICU) untuk perbandingan string linguistik pada semua platform yang didukung. Untuk informasi selengkapnya, lihat String dan Unicode Standard dan globalisasi .NET dan ICU.

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 pengaturan lokal thread 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 huruf besar-kecil dalam .NET berubah ketika kultur 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 pengurutan antara budaya Inggris A.S. ("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 yang tidak membedakan huruf besar/kecil dan menggunakan budaya saat ini sama dengan perbandingan yang peka terhadap budaya, dengan pengecualian bahwa mereka mengabaikan perbedaan huruf besar/kecil sesuai dengan budaya utas saat ini. Perilaku ini juga dapat memanifestasikan dirinya dalam urutan pengurutan.

Perbandingan yang menggunakan semantik budaya saat ini adalah default untuk metode berikut:

Bagaimanapun, kami sarankan Anda memanggil overload yang memiliki StringComparison parameter untuk memperjelas maksud pemanggilan metode.

Bug yang 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 Turkish-I.

Untuk hampir semua alfabet Latin, termasuk bahasa Inggris A.S. , karakter "i" (\u0069) adalah versi huruf kecil dari karakter "I" (\u0049). Aturan penulisan ini dengan cepat menjadi standar bagi seseorang yang memprogram dalam lingkungan pemrograman seperti itu. Namun, alfabet Turki ("tr-TR") menyertakan karakter "I dengan titik" "İ" (\u0130), yang merupakan versi huruf besar dari "i". Turki juga menyertakan karakter "i tanpa titik" huruf kecil, "ı" (\u0131), yang menjadi huruf kapital "I". Perilaku ini juga terjadi pada budaya Azerbaijan ("az").

Oleh karena itu, asumsi yang dibuat tentang mengkapitalisasi "i" atau menghuruf kecilkan "I" tidak berlaku untuk semua budaya. Jika Anda menggunakan overload default untuk rutinitas perbandingan string, rutinitas tersebut akan dipengaruhi oleh perbedaan budaya. Jika data yang akan dibandingkan adalah non-linguistik, menggunakan overload default dapat menghasilkan hasil yang tidak diinginkan, seperti yang diperlihatkan oleh upaya berikut untuk melakukan perbandingan yang tidak peka terhadap besar-kecilnya huruf dari string "bill" dan "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

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 membedakan 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 harus 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 StringComparison.Ordinal atau nilai StringComparison.OrdinalIgnoreCase dalam panggilan metode menandakan perbandingan non-linguistik di mana karakteristik bahasa alami diabaikan. Metode yang dipanggil dengan nilai StringComparison ini mendasarkan keputusan operasi string pada perbandingan byte sederhana alih-alih tabel casing atau kesetaraan yang diparameterkan berdasarkan 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 runtime strcmp 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.

Pembanding OrdinalIgnoreCase masih beroperasi berdasarkan karakter per karakter, tetapi mengabaikan perbedaan huruf besar-kecil saat melakukan operasi. Di bawah pembanding OrdinalIgnoreCase, pasangan karakter 'd' dan 'D' dibandingkan sebagai sama, seperti halnya 'á' dan 'Á'. Tetapi karakter 'a' yang tidak beraksen dinilai tidak sama dengan karakter 'á' yang beraksen.

Beberapa contoh ini disediakan dalam tabel berikut:

String 1 String 2 Ordinal Perbandingan OrdinalIgnoreCase Perbandingan
"dog" "dog" sama dengan sama dengan
"dog" "Dog" Tidak setara sama dengan
"resume" "résumé" Tidak setara Tidak setara

Unicode juga memungkinkan string memiliki beberapa representasi dalam memori yang berbeda. Misalnya, é-akut (é) dapat diwakili dengan dua cara yang berbeda:

  • Satu karakter harfiah 'é' (juga ditulis sebagai '\u00E9').
  • Karakter literal yang tidak beraksen 'e' diikuti oleh karakter pengubah aksen penggabungan '\u0301'.

Ini berarti bahwa empat string berikut semuanya ditampilkan sebagai "résumé", meskipun potongan konstituennya berbeda. String menggunakan kombinasi karakter literal 'é' atau karakter literal yang tidak beraksen 'e' ditambah pengubah aksen kombinasi '\u0301'.

  • "r\u00E9sum\u00E9"
  • "r\u00E9sume\u0301"
  • "re\u0301sum\u00E9"
  • "re\u0301sume\u0301"

Dalam perbandingan ordinal, tidak ada string ini yang dianggap sama satu sama lain. Ini karena semuanya berisi urutan karakter yang mendasar yang berbeda, meskipun ketika dirender ke layar, semuanya terlihat sama.

Saat menjalankan operasi string.IndexOf(..., StringComparison.Ordinal), runtime mencari kecocokan substring yang tepat. Hasilnya adalah sebagai berikut.

Console.WriteLine("resume".IndexOf('e', StringComparison.Ordinal)); // "resume": prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf('e', StringComparison.Ordinal)); // "résumé": prints '1'
Console.WriteLine("resume".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "resume": prints '1'
Console.WriteLine("r\u00E9sum\u00E9".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '-1'
Console.WriteLine("r\u00E9sume\u0301".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '5'
Console.WriteLine("re\u0301sum\u00E9".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '1'
Console.WriteLine("re\u0301sume\u0301".IndexOf('e', StringComparison.OrdinalIgnoreCase)); // "résumé": prints '1'
Sub IndexOfExample()
    Console.WriteLine("resume".IndexOf("e"c, StringComparison.Ordinal)) ' "resume": prints '1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '-1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '5'
    Console.WriteLine(("re" & ChrW(&H301) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '1'
    Console.WriteLine(("re" & ChrW(&H301) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.Ordinal)) ' "résumé": prints '1'
    Console.WriteLine("resume".IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "resume": prints '1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '-1'
    Console.WriteLine(("r" & ChrW(&HE9) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '5'
    Console.WriteLine(("re" & ChrW(&H301) & "sum" & ChrW(&HE9)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '1'
    Console.WriteLine(("re" & ChrW(&H301) & "sume" & ChrW(&H301)).IndexOf("e"c, StringComparison.OrdinalIgnoreCase)) ' "résumé": prints '1'
End Sub

Rutinitas pencarian dan perbandingan ordinal tidak pernah terpengaruh oleh pengaturan budaya utas saat ini.

String di .NET dapat mengandung karakter null yang disematkan (dan karakter tidak tercetak lainnya). Salah satu perbedaan paling jelas antara perbandingan ordinal dan peka budaya (termasuk perbandingan yang menggunakan budaya invarian) menyangkut penanganan karakter null yang disematkan dalam string. Karakter ini diabaikan ketika Anda menggunakan metode String.Compare dan String.Equals untuk melakukan perbandingan yang peka terhadap budaya (termasuk perbandingan yang menggunakan budaya invariant). 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.EndsWithString.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 membahas karakter ASCII, kebijakan ini setara dengan StringComparison.Ordinal, kecuali bahwa kebijakan ini mengabaikan pengaturan huruf ASCII seperti biasanya. Oleh karena itu, setiap karakter dalam [A, Z] (\u0041-\u005A) cocok dengan karakter yang sesuai dalam [a,z] (\u0061-\007A). Casing di luar rentang ASCII menggunakan tabel budaya invariant. 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 gunakan 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, kami sarankan Anda memanggil fungsi overload yang memiliki parameter StringComparison.

Perbandingan string linguistik

Rutinitas pencarian dan perbandingan linguistik menguraikan string menjadi elemen kolase dan melakukan pencarian atau perbandingan pada elemen-elemen ini. Belum tentu ada pemetaan 1:1 antara karakter string dan elemen kolase konstituennya. Misalnya, string dengan panjang 2 dapat terdiri dari hanya satu elemen pengurutan. Ketika dua string dibandingkan dengan cara yang sadar linguistik, pembanding memeriksa apakah elemen kolase dua string memiliki arti semantik yang sama, bahkan jika karakter harfiah string berbeda.

Pertimbangkan string "résumé" dan empat representasi berbeda yang dijelaskan di bagian sebelumnya. Tabel berikut menunjukkan setiap representasi yang dipecah ke dalam elemen pengurutannya.

String Sebagai elemen kolase
"r\u00E9sum\u00E9" "r" + "\u00E9" + "s" + "u" + "m" + "\u00E9"
"r\u00E9sume\u0301" "r" + "\u00E9" + "s" + "u" + "m" + "e\u0301"
"re\u0301sum\u00E9" "r" + "e\u0301" + "s" + "u" + "m" + "\u00E9"
"re\u0301sume\u0301" "r" + "e\u0301" + "s" + "u" + "m" + "e\u0301"

Elemen penyusunan terkait dengan apa yang mungkin dianggap pembaca sebagai satu karakter atau sekelompok karakter. Ini secara konseptual mirip dengan kluster grapheme tetapi mencakup payung yang agak lebih besar.

Di bawah perbandingan linguistik, kecocokan persis tidak diperlukan. Elemen kolasi justru dibandingkan berdasarkan makna semantiknya. Misalnya, pembanding linguistik memperlakukan substring "\u00E9" dan "e\u0301" sebagai sama karena secara semantik keduanya berarti "huruf kecil e dengan pengubah aksen akut." Ini memungkinkan metode IndexOf mencocokkan substring "e\u0301" dalam string yang lebih besar yang berisi substring "\u00E9" yang secara semantik setara, seperti yang ditunjukkan dalam sampel kode berikut.

Console.WriteLine("r\u00E9sum\u00E9".IndexOf("e")); // "résumé": prints '-1' (not found)
Console.WriteLine("r\u00E9sum\u00E9".IndexOf("\u00E9")); // "résumé": prints '1'
Console.WriteLine("\u00E9".IndexOf("e\u0301")); // prints '0'
Sub IndexOfStringExample()
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf("e")) ' "résumé": prints '-1' (not found)
    Console.WriteLine(("r" & ChrW(&HE9) & "sum" & ChrW(&HE9)).IndexOf(ChrW(&HE9).ToString())) ' "résumé": prints '1'
    Console.WriteLine(ChrW(&HE9).ToString().IndexOf("e" & ChrW(&H301))) ' prints '0'
End Sub

Sebagai konsekuensi dari ini, dua string dengan panjang yang berbeda dapat dibandingkan sama jika perbandingan linguistik digunakan. Penelepon harus berhati-hati untuk tidak menangani logika kasus khusus yang berkaitan dengan panjang string dalam skenario tersebut.

Rutinitas pencarian dan perbandingan yang sadar budaya adalah bentuk khusus dari rutinitas pencarian dan perbandingan linguistik. Di bawah pembanding yang sadar budaya, konsep elemen kolasi diperluas untuk menyertakan informasi khusus dari budaya yang ditentukan.

Misalnya, dalam alfabet Hungaria, ketika dua karakter <dz> muncul secara back-to-back, mereka dianggap sebagai huruf unik mereka sendiri berbeda dari <d> atau <z>. Ini berarti bahwa ketika <dz> terlihat dalam string, pembanding sadar budaya Hungaria memperlakukannya sebagai elemen kolase tunggal.

String Sebagai elemen kolase Komentar
"endz" "e" + "n" + "d" + "z" (menggunakan perbandingan linguistik standar)
"endz" "e" + "n" + "dz" (menggunakan perbandingan sadar budaya Hungaria)

Saat menggunakan pembanding yang menyadari budaya Hungaria, teks "endz"tidak berakhir dengan substring "z", karena <dz> dan <z> dianggap sebagai elemen pengurutan dengan arti semantik yang berbeda.

// Set thread culture to Hungarian
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU");
Console.WriteLine("endz".EndsWith("z")); // Prints 'False'

// Set thread culture to invariant culture
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Console.WriteLine("endz".EndsWith("z")); // Prints 'True'
' Set thread culture to Hungarian
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("hu-HU")
Console.WriteLine("endz".EndsWith("z")) ' Prints 'False'

' Set thread culture to invariant culture
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture
Console.WriteLine("endz".EndsWith("z")) ' Prints 'True'

Nota

  • Perilaku: Pembanding yang peka linguistik dan budaya dapat mengalami penyesuaian perilaku secara berkala. ICU dan fasilitas NLS Windows yang lebih lama diperbarui untuk mengakomodasi perubahan bahasa dunia. Untuk informasi selengkapnya, lihat posting blog Perubahan data Lokal (budaya). Perilaku perbandingan Ordinal tidak akan pernah berubah karena melakukan pencarian dan perbandingan bitwise yang tepat. Namun, perilaku perbandingan OrdinalIgnoreCase dapat berubah saat Unicode tumbuh untuk mencakup lebih banyak set karakter dan memperbaiki kelalaian dalam data casing yang ada.
  • Penggunaan: Pembanding StringComparison.InvariantCulture dan StringComparison.InvariantCultureIgnoreCase merupakan pembanding linguistik yang tidak sadar budaya. Artinya, pembanding ini memahami konsep bahwa karakter beraksen seperti é dapat memiliki beberapa representasi dasar yang berbeda, dan semua representasi tersebut harus diperlakukan sama. Tetapi pembanding linguistik yang tidak sadar budaya tidak akan mengandung penanganan khusus untuk <dz> yang berbeda dari <d> atau <z>, seperti yang ditunjukkan di atas. Mereka juga tidak akan memperlakukan karakter secara khusus seperti Eszett Jerman (ß).

.NET juga menawarkan mode globalisasi yang invarian. Mode pilihan ini menonaktifkan jalur kode yang menangani rutinitas pencarian dan perbandingan bahasa. Dalam mode ini, semua operasi menggunakan perilaku Ordinal atau OrdinalIgnoreCase, terlepas dari argumen CultureInfo atau StringComparison yang disediakan oleh pemanggil. Untuk informasi selengkapnya, lihat Opsi konfigurasi runtime untuk globalisasi dan Mode Invarian Globalisasi .NET Core.

Operasi string yang menggunakan kultur invarian

Perbandingan dengan budaya invarian menggunakan properti CompareInfo yang dikembalikan oleh properti statis CultureInfo.InvariantCulture. 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 budaya invarian juga menggunakan properti statis CompareInfo yang dikembalikan oleh properti statis CultureInfo.InvariantCulture untuk informasi perbandingan. 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 sekumpulan byte. Objek CultureInfo.InvariantCulture.CompareInfo membuat metode Compare menginterpretasikan sekumpulan karakter tertentu sebagai setara. Misalnya, kesetaraan berikut berlaku di bawah budaya invarian:

Budaya Tidak Berubah: 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 {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

Saat menafsirkan nama file, cookie, atau apa pun di mana kombinasi seperti "å" dapat muncul, perbandingan ordinal masih menawarkan perilaku yang paling transparan dan pas.

Secara keseluruhan, budaya invarian memiliki sedikit sifat yang membuatnya berguna untuk perbandingan. Ini melakukan perbandingan dengan cara yang relevan secara linguistik, yang mencegahnya menjamin kesetaraan simbolis penuh, tetapi tidak menjadi pilihan yang tepat untuk ditampilkan di budaya mana pun. Salah satu dari beberapa alasan untuk digunakan StringComparison.InvariantCulture sebagai perbandingan adalah untuk mempertahankan data yang dipesan untuk tampilan lintas budaya yang identik. 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 invariant.

Cara memilih anggota StringComparison

Tabel berikut menguraikan pemetaan dari konteks string semantik ke StringComparison anggota enumerasi:

Data Informasi Perilaku Perbandingan System.String yang Sesuai

nilai
Pengidentifikasi internal yang peka terhadap penggunaan huruf besar dan kecil.

Pengidentifikasi peka huruf besar/kecil dalam standar seperti XML dan HTTP.

Pengaturan keamanan yang sensitif terhadap huruf besar/kecil.
Pengidentifikasi non-linguistik, di mana byte cocok persis. Ordinal
Pengidentifikasi internal yang tidak sensitif 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, menangani nama).

Pengaturan terkait keamanan yang tidak peka huruf besar/kecil.
Pengidentifikasi non-linguistik, di mana kasus tidak relevan. OrdinalIgnoreCase
Beberapa data yang tetap ada dan relevan secara linguistik.

Tampilan data linguistik yang memerlukan urutan pengurutan tetap.
Data agnostik budaya yang masih relevan secara linguistik. InvariantCulture

-atau-

InvariantCultureIgnoreCase
Data ditampilkan kepada pengguna.

Sebagian besar input pengguna.
Data yang memerlukan kebiasaan linguistik lokal. CurrentCulture

-atau-

CurrentCultureIgnoreCase

Implikasi keamanan

Jika aplikasi Anda menggunakan API string untuk pemfilteran atau kontrol akses, gunakan perbandingan ordinal. Perbandingan linguistik berdasarkan budaya saat ini dapat menghasilkan hasil tak terduga yang bervariasi menurut platform dan lokal. Pola kode seperti berikut mungkin rentan terhadap eksploitasi keamanan:

//
// THIS SAMPLE CODE IS INCORRECT.
// DO NOT USE IT IN PRODUCTION.
//
bool ContainsHtmlSensitiveCharacters(string input)
{
    if (input.IndexOf("<") >= 0) { return true; }
    if (input.IndexOf("&") >= 0) { return true; }
    return false;
}
'
' THIS SAMPLE CODE IS INCORRECT.
' DO NOT USE IT IN PRODUCTION.
'
Function ContainsHtmlSensitiveCharacters(input As String) As Boolean
    If input.IndexOf("<") >= 0 Then Return True
    If input.IndexOf("&") >= 0 Then Return True
    Return False
End Function

Karena metode ini string.IndexOf(string) menggunakan pencarian linguistik secara default, mungkin saja string mengandung literal '<' atau '&', dan mungkin saja string.IndexOf(string) mengembalikan -1, menunjukkan bahwa substring pencarian tidak ditemukan. Aturan analisis kode CA1307 dan CA1309 menandai situs panggilan tersebut dan memperingatkan pengembang bahwa ada potensi masalah.

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 penting untuk interpretasi string, semua panggilan metode ini harus diperiksa untuk menentukan apakah string harus ditafsirkan sesuai dengan budaya saat ini, atau dipisahkan dari konteks 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, mengabaikan jenis kana, dan sebagainya) melalui enumerasi penanda 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 IComparable antarmuka dan IComparable<T> menerapkan metode ini. Karena tidak menawarkan opsi StringComparison parameter, jenis penerapan sering kali memungkinkan pengguna menentukan StringComparer di konstruktor mereka. Contoh berikut mendefinisikan FileName kelas yang konstruktor kelasnya menyertakan StringComparer parameter. Objek ini StringComparer kemudian digunakan dalam FileName.CompareTo metode .

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 statis atau instans Equals, atau dengan menggunakan operator kesetaraan statis. Kelebihan beban dan operator menggunakan perbandingan ordinal secara default. Namun, kami masih menyarankan agar Anda memanggil fungsi overload yang secara eksplisit menentukan jenis StringComparison, bahkan jika Anda ingin melakukan perbandingan ordinal; ini mempermudah pencarian kode untuk interpretasi string tertentu.

String.ToUpper dan String.ToLower

Interpretasi default: StringComparison.CurrentCulture.

Berhati-hatilah saat Anda menggunakan metode String.ToUpper() dan String.ToLower(), karena memaksa string ke huruf besar atau huruf kecil sering digunakan sebagai normalisasi kecil untuk membandingkan string terlepas dari besar-kecilnya huruf. 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 dilakukan menggunakan StringComparison.OrdinalIgnoreCase secara perilaku terdiri dari dua pemanggilan: memanggil ToUpperInvariant pada kedua argumen string, dan melakukan perbandingan menggunakan StringComparison.Ordinal.

Kelebihan beban juga tersedia untuk mengonversi ke huruf besar dan huruf kecil dalam budaya tertentu, dengan meneruskan CultureInfo objek yang mewakili budaya tersebut ke metode .

Char.ToUpper dan Char.ToLower

Interpretasi default: StringComparison.CurrentCulture.

Metode Char.ToUpper(Char) dan Char.ToLower(Char) berfungsi mirip dengan String.ToUpper() metode dan String.ToLower() yang dijelaskan di bagian sebelumnya.

String.StartsWith dan String.EndsWith

Interpretasi default: StringComparison.CurrentCulture (ketika parameter pertama adalah string), atau StringComparison.Ordinal (ketika parameter pertama adalah char).

Ada ketidakkonsistensian dalam bagaimana kelebihan beban default metode ini melakukan perbandingan. Kelebihan beban yang menerima char parameter melakukan perbandingan ordinal, tetapi kelebihan beban yang menerima string parameter melakukan perbandingan sensitif terhadap budaya dan dapat mengabaikan karakter non-pencetakan.

String.IndexOf dan 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 default String.IndexOf dan String.LastIndexOf yang menyertakan parameter String melakukan perbandingan yang sensitif terhadap budaya.

Jika Anda memanggil String.IndexOf(String) metode atau String.LastIndexOf(String) dan meneruskannya string untuk menemukan dalam instans saat ini, kami sarankan Anda memanggil kelebihan beban yang secara eksplisit menentukan jenisnya StringComparison . Kelebihan beban yang menyertakan Char argumen tidak memungkinkan Anda menentukan StringComparison jenis.

String.Contains

Interpretasi default: StringComparison.Ordinal.

Tidak seperti String.IndexOf, metode String.Contains ini menggunakan perbandingan ordinal secara default untuk kedua overload char dan string. Namun, Anda masih harus meneruskan argumen eksplisit StringComparison ketika maksud penting, untuk membuat perilaku jelas di lokasi panggilan.

MemoryExtensions.AsSpan.IndexOfAnydan jenisnya SearchValues<T>

.NET 8 memperkenalkan SearchValues<T> tipe, yang menyediakan solusi yang dioptimalkan untuk mencari set karakter atau byte tertentu dalam segmen.

Jika Anda membandingkan string dengan serangkaian nilai tetap yang diketahui berulang kali, pertimbangkan untuk menggunakan metode SearchValues<T>.Contains(T) alih-alih perbandingan berantai atau pendekatan berbasis LINQ. SearchValues<T> dapat melakukan precompute struktur pencarian internal dan mengoptimalkan logika perbandingan berdasarkan nilai yang disediakan. Untuk melihat manfaat performa, buat dan cache SearchValues<string> instans sekali, lalu gunakan kembali untuk perbandingan:

using System.Buffers;

namespace ExampleCode;

internal partial class DemoCode
{
    private static readonly SearchValues<string> Commands =
        SearchValues.Create(
            ["start", "run", "go", "begin", "commence"],
            StringComparison.OrdinalIgnoreCase);

    void ProcessCommand(string command)
    {
        if (Commands.Contains(command))
        {
            // ...
        }
    }
}
Imports System.Buffers

Namespace ExampleCode
    Partial Friend Class DemoCode

        Private Shared ReadOnly Commands As SearchValues(Of String) =
            SearchValues.Create(
                {"start", "run", "go", "begin", "commence"},
                StringComparison.OrdinalIgnoreCase)

        Sub ProcessCommand(command As String)
            If Commands.Contains(command) Then
                ' ...
            End If
        End Sub

    End Class
End Namespace

Di .NET 9, SearchValues diperluas untuk mendukung pencarian substring dalam string yang lebih besar. Misalnya, lihat SearchValues ekspansi.

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 statis yang mengembalikan instance StringComparer yang metodenya StringComparer.Compare melakukan jenis perbandingan string berikut:

Array.Sort dan Array.BinarySearch

Interpretasi default: StringComparison.CurrentCulture.

Saat Anda menyimpan data apa pun dalam koleksi, atau membaca data yang bertahan dari file atau database ke dalam koleksi, mengalihkan budaya saat ini dapat membatalkan invarian dalam koleksi. Metode ini Array.BinarySearch mengasumsikan bahwa elemen dalam array yang akan dicari sudah diurutkan. Untuk mengurutkan elemen string apa pun dalam array, Array.Sort metode memanggil String.Compare metode untuk mengurutkan elemen individual. Menggunakan pembanding yang sensitif terhadap pengaturan lokal dapat berbahaya jika pengaturan lokal berubah antara waktu array diurutkan dan isi array tersebut dicari. Misalnya, dalam kode, penyimpanan, dan pengambilan berikut beroperasi pada comparer yang disediakan secara implisit oleh Thread.CurrentThread.CurrentCulture properti . Jika budaya dapat berubah antara panggilan ke StoreNames dan DoesNameExist, dan terutama jika konten array disimpan di suatu tempat 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 bertahan dan dipindahkan di seluruh budaya, dan pengurutan digunakan untuk menyajikan data ini kepada pengguna, Anda mungkin 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 budaya 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: Hashtable konstruktor

String hashing menyediakan contoh kedua operasi yang dipengaruhi oleh cara string dibandingkan.

Contoh berikut menginstansiasi objek Hashtable dengan meneruskan objek StringComparer yang dikembalikan oleh properti StringComparer.OrdinalIgnoreCase. Karena kelas StringComparer yang berasal dari StringComparer mengimplementasikan IEqualityComparer antarmuka, metodenya GetHashCode 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

Contoh koleksi: SortedSet<T> dan List<T>.Sort

Masalah sensitivitas lokal yang sama berlaku saat membuat instans kumpulan string yang diurutkan atau mengurutkan koleksi berbasis string yang ada. Selalu tentukan perbandingan eksplisit:

// Words to sort
string[] values = [ "able", "ångström", "apple", "Æble",
            "Windows", "Visual Studio" ];

//
// Potentially incorrect code - behavior might vary based on locale.
//
SortedSet<string> mySet = [.. values]; // No comparer specified

List<string> list = [.. values];
list.Sort(); // No comparer specified

//
// Corrected code - uses ordinal sorting; doesn't vary by locale.
//
SortedSet<string> mySet2 = new(values, StringComparer.Ordinal);

List<string> list2 = [.. values];
list2.Sort(StringComparer.Ordinal);
' Words to sort
Dim values As String() = {"able", "ångström", "apple", "Æble",
                          "Windows", "Visual Studio"}

'
' Potentially incorrect code - behavior might vary based on locale.
'
Dim mySet As New SortedSet(Of String)(values) ' No comparer specified

Dim list As New List(Of String)(values)
list.Sort() ' No comparer specified

'
' Corrected code - uses ordinal sorting; doesn't vary by locale.
'
Dim mySet2 As New SortedSet(Of String)(values, StringComparer.Ordinal)

Dim list2 As New List(Of String)(values)
list2.Sort(StringComparer.Ordinal)

Perbedaan antara .NET dan .NET Framework

.NET dan .NET Framework menangani globalisasi secara berbeda. .NET Framework di Windows menggunakan fasilitas Dukungan Bahasa Nasional (NLS) sistem operasi untuk perbandingan string linguistik. .NET menggunakan pustaka Komponen Internasional untuk Unicode (ICU) untuk perbandingan string linguistik pada semua platform yang didukung.

Karena ICU dan NLS menerapkan logika yang berbeda dalam perbandingan linguistik mereka, hasil metode string yang menggunakan perbandingan sensitif budaya dapat berbeda antara .NET dan .NET Framework. Ini penting untuk metode apa pun yang menggunakan perbandingan linguistik secara default, termasuk:

Nota

Ini bukan daftar lengkap API yang terpengaruh.

Salah satu perbedaan utama adalah dalam menangani karakter null yang tertanam dan karakter kontrol lainnya. Saat Anda menggunakan perbandingan linguistik di bawah NLS, beberapa karakter kontrol seperti karakter null (\0) mungkin diperlakukan sebagai tidak dapat diabaikan dalam konteks perbandingan tertentu. Dalam konteks ICU, karakter ini diperlakukan sebagai karakter sebenarnya dalam string. Ini dapat menyebabkan string.IndexOf(string) mengembalikan hasil yang berbeda ketika string pencarian berisi karakter null.

Misalnya, kode berikut dapat menghasilkan jawaban yang berbeda tergantung pada runtime saat ini:

const string greeting = "Hel\0lo";
Console.WriteLine($"{greeting.IndexOf("\0")}");

// The snippet prints:
//
// '3' when running on .NET Framework and .NET Core 2.x - 3.x (Windows)
// '0' when running on .NET 5 or later (Windows)
// '0' when running on .NET Core 2.x - 3.x or .NET 5 (non-Windows)
// '3' when running on .NET Core 2.x or .NET 5+ (in invariant mode)
Const greeting As String = "Hel" & vbNullChar & "lo"
Console.WriteLine($"{greeting.IndexOf(CStr(vbNullChar))}")

' The snippet prints:
'
' '3' when running on .NET Framework and .NET Core 2.x - 3.x (Windows)
' '0' when running on .NET 5 or later (Windows)
' '0' when running on .NET Core 2.x - 3.x or .NET 5 (non-Windows)
' '3' when running on .NET Core 2.x or .NET 5+ (in invariant mode)

Cara terbaik untuk menghindari kejutan lintas platform dan lintas implementasi ini adalah selalu meneruskan argumen eksplisit StringComparison ke metode perbandingan string, dan untuk menggunakan StringComparison.Ordinal atau StringComparison.OrdinalIgnoreCase untuk perbandingan non-linguistik.

Jika Anda memigrasikan aplikasi dari .NET Framework ke .NET dan mengandalkan perilaku NLS warisan di Windows, Anda dapat mengonfigurasi aplikasi untuk menggunakan NLS. Untuk informasi selengkapnya, lihat globalisasi .NET dan ICU.

Lihat juga