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.

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:

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 kembali 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. 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:

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:

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

Lihat juga