.NET'te dizeleri karşılaştırmak için en iyi yöntemler

.NET, yerelleştirilmiş ve genelleştirilmiş uygulamalar geliştirmek için kapsamlı destek sağlar ve dizeleri sıralama ve görüntüleme gibi yaygın işlemleri gerçekleştirirken geçerli kültürün veya belirli bir kültürün kurallarını uygulamayı kolaylaştırır. Ancak dizeleri sıralamak veya karşılaştırmak her zaman kültüre duyarlı bir işlem değildir. Örneğin, bir uygulama tarafından dahili olarak kullanılan dizelerin genellikle tüm kültürlerde aynı şekilde işlenmeleri gerekir. XML etiketleri, HTML etiketleri, kullanıcı adları, dosya yolları ve sistem nesnelerinin adları gibi kültürden bağımsız dize verileri kültüre duyarlıymış gibi yorumlanırsa, uygulama kodu küçük hatalar, zayıf performans ve bazı durumlarda güvenlik sorunlarıyla karşılaşabilir.

Bu makalede .NET'teki dize sıralama, karşılaştırma ve büyük/küçük harfe çevirme yöntemleri incelenerek, uygun bir dize işleme yöntemi seçmeye yönelik öneriler sunulur ve dize işleme yöntemleri hakkında ek bilgiler sağlanır.

Dize kullanımı için Öneriler

.NET ile geliştirme yaparken, dizeleri karşılaştırırken bu önerileri izleyin.

İpucu

Dizeyle ilgili çeşitli yöntemler karşılaştırma gerçekleştirir. Örnek olarak String.Equals, String.Compare, String.IndexOfve String.StartsWithverilebilir.

Dizeleri karşılaştırırken aşağıdaki uygulamalardan kaçının:

  • Dize işlemleri için dize karşılaştırma kurallarını açıkça veya örtük olarak belirtmeyen aşırı yüklemeler kullanmayın.
  • Çoğu durumda tabanlı StringComparison.InvariantCulture dize işlemlerini kullanmayın. Birkaç özel durumdan biri, dilsel olarak anlamlı ancak kültürel olarak belirsiz verileri kalıcı hale getirmenizdir.
  • veya CompareTo yönteminin aşırı yüklemesini kullanmayın ve iki dizenin String.Compare eşit olup olmadığını belirlemek için sıfır dönüş değerini test edin.

Dize karşılaştırmalarını açıkça belirtme

.NET'teki dize işleme yöntemlerinin çoğu aşırı yüklenmiştir. Genellikle bir veya daha fazla aşırı yükleme varsayılan ayarları kabul ederken diğerleri varsayılanları kabul etmez ve bunun yerine dizelerin tam olarak nasıl karşılaştırılacağını veya değiştirileceğini tanımlar. Varsayılanlara dayanmayan yöntemlerin çoğu, kültür ve büyük/küçük harfe göre dize karşılaştırması kurallarını açıkça belirten bir numaralandırma olan türünde StringComparisonbir parametre içerir. Aşağıdaki tablo, StringComparison numaralandırma üyelerini açıklar.

StringComparison üyesi Açıklama
CurrentCulture Geçerli kültürü kullanarak büyük/küçük harfe duyarlı bir karşılaştırma gerçekleştirir.
CurrentCultureIgnoreCase Geçerli kültürü kullanarak büyük/küçük harfe duyarsız bir karşılaştırma gerçekleştirir.
InvariantCulture Sabit kültürü kullanarak büyük/küçük harfe duyarlı bir karşılaştırma gerçekleştirir.
InvariantCultureIgnoreCase Sabit kültürü kullanarak büyük/küçük harfe duyarsız bir karşılaştırma gerçekleştirir.
Ordinal Sıralı bir karşılaştırma gerçekleştirir.
OrdinalIgnoreCase Büyük/küçük harfe duyarlı olmayan sıralı bir karşılaştırma gerçekleştirir.

Örneğin, bir karakteri veya bir dizeyi eşleştiren bir IndexOf nenesindeki alt dizine ait dizeyi döndüren String yönteminin dokuz aşırı yüklemesi vardır:

Aşağıdaki nedenlerle varsayılan değerleri kullanmayan bir aşırı yükleme seçmenizi öneririz:

  • Varsayılan parametreleri olan bazı aşırı yüklemeler (dize örneğinde bir Char arayanlar) sıralı bir karşılaştırma gerçekleştirirken diğerleri (dize örneğinde bir dize arayanlar), kültüre duyarlıdır. Hangi yöntemin hangi varsayılan değeri kullandığını hatırlamak zordur ve aşırı yüklemeleri karıştırmak kolaydır.

  • Yöntem çağrıları için varsayılan değerleri kullanan kodun amacı net değil. Varsayılan değerlere dayalı olan aşağıdaki örnekte, geliştiricinin aslında iki dizenin sıralı veya dilsel karşılaştırmasını mı amaçladığını ya da ile "https" arasındaki url.Scheme bir büyük/küçük harf farkının eşitlik testinin döndürmesine falseneden olup olmadığını bilmek zordur.

    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
    

Genel olarak, kodun amacını belirsiz hale getirdiğinden varsayılanlara güvenmeyen bir yöntemi çağırmanızı öneririz. Bu sayede kod daha okunabilir olur ve hata ayıklaması ve bakımı daha kolay hale gelir. Aşağıdaki örnek, önceki örnekle ilgili oluşturulan soruları ele alır. Sıralı karşılaştırmanın kullanıldığını ve büyük/küçük harfteki farkların yok sayıldığını belirtir.

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

Dize karşılaştırmasının ayrıntıları

Dize karşılaştırma, özellikle sıralama ve eşitlik testi gibi, dizeyle ilgili pek çok işlemin temelidir. Dizeler belirli bir düzende sıralanır: Eğer dizelerin sıralı bir listesinde "my" dizesi "string" dizesinden önce görünüyorsa, "my" dizesi "string" dizesi ile karşılaştırıldığında daha küçük veya eşit olmalıdır. Ek olarak, karşılaştırma dolaylı olarak eşitliği tanımlar. Karşılaştırma işlemi, eşit olarak gördüğü dizeler için sıfır döndürür. İki dizenin de diğerinden daha az olmaması iyi bir yorumdur. Dizeleri içeren en anlamlı işlemler, şu yordamların birini veya her ikisini içerir: başka bir dize ile karşılaştırma ve iyi tanımlanmış bir sıralama işlemini yürütme.

Not

Sıralama Ağırlığı Tablolarını, Windows işletim sistemleri için sıralama ve karşılaştırma işlemlerinde kullanılan karakter ağırlıkları hakkında bilgi içeren metin dosyaları kümesini ve Linux ve macOS için sıralama ağırlığı tablosunun en son sürümü olan Varsayılan Unicode Harmanlama Öğesi Tablosu'nu indirebilirsiniz. Linux ve macOS'ta sıralama ağırlığı tablosunun belirli sürümü, sistemde yüklü Unicode kitaplıkları için Uluslararası Bileşenler sürümüne bağlıdır. ICU sürümleri ve uyguladıkları Unicode sürümleri hakkında bilgi için bkz . ICU İndirme.

Ancak, iki dizeyi eşitlik veya sıralama düzeni için değerlendirmek tek ve doğru bir sonuç vermez; sonuç, dizeleri karşılaştırmak için kullanılan ölçütlere bağlıdır. Özellikle, sıralı olan veya geçerli kültürün veya sabit kültürün (İngilizce dilini temel alan yerel ayardan bağımsız bir kültür ) büyük/küçük harf ve sıralama kurallarını temel alan dize karşılaştırmaları farklı sonuçlar verebilir.

Ayrıca, .NET'in farklı sürümlerini kullanan veya farklı işletim sistemlerinde veya işletim sistemi sürümlerinde .NET kullanan dize karşılaştırmaları farklı sonuçlar döndürebilir. Daha fazla bilgi için bkz . Dizeler ve Unicode Standardı.

Geçerli kültürü kullanan dize karşılaştırmaları

Ölçütlerden biri, dizeleri karşılaştırırken geçerli kültüre ait kuralların kullanılmasını içerir. Geçerli kültürü temel alan karşılaştırmalar iş parçacığının geçerli kültürünü veya yerel ayarlarını kullanır. Kültür kullanıcı tarafından ayarlanmadıysa, varsayılan olarak işletim sisteminin ayarına ayarlanır. Veriler dilsel olduğunda ve kültüre duyarlı kullanıcı etkileşimini yansıttığında her zaman geçerli kültürü temel alan karşılaştırmalar kullanmalısınız.

Ancak kültür değiştiğinde .NET'teki karşılaştırma ve büyük/küçük harf davranışı değişir. Bu, bir uygulama geliştirildiği bilgisayardakinden farklı bir kültüre sahip olan başka bir bilgisayarda yürütüldüğünde veya uygulamayı yürüten iş parçacığı kültürünü değiştirdiğinde gerçekleşir. Bu davranış kasıtlıdır, ancak çoğu geliştirici için belirgin değildir. Aşağıdaki örnek, sıralama düzeninin A.B.D. İngilizce ("en-US") ve İsveççe ("sv-SE") kültürleri arasındaki farkını gösterir. "ångström", "Windows" ve "Visual Studio" sözcüklerinin sıralanmış dize dizilerinde farklı konumlarda göründüğüne dikkat edin.

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

Geçerli kültürü kullanan ve büyük/küçük harfe duyarlı olmayan karşılaştırmalar, kültüre duyarlı karşılaştırmalarla aynıdır, ancak iş parçacığının geçerli kültürü tarafından belirtilen büyük/küçük harfi göz ardı eder. Bu davranış kendisini sıralama düzenlerinde de gösterebilir.

Geçerli kültürün semantiklerini kullanan karşılaştırmalar aşağıdaki yöntemler için varsayılan değerdir:

Her durumda, metodun çağrı amacını belli etmek için StringComparison parametresi olan bir aşırı yüklemeyi çağırmanızı öneririz.

Dilsel olmayan veriler dilsel olarak yorumlandığında veya belirli bir kültürdeki dize verileri başka bir kültürün kuralları kullanılarak yorumlandığında küçük ve küçük olmayan hatalar oluşabilir. Kurallı örnek, Türkçe-I sorunudur.

A.B.D. İngilizce dahil olmak üzere neredeyse tüm Latin alfabelerinde, "i" karakteri (\u0069) "I" karakterinin (\u0049) küçük harfli halidir. Bu büyük/küçük harf kuralı bunun gibi bir kültürde programlama yapan birisi için hızla varsayılan hale gelir. Ancak, Türkçe ("tr-TR") alfabesi, "i" karakterinin büyük harfli hali olan "Noktalı I" karakteri, yani "İ" (\u0130) içerir. Türkçe ayrıca büyük harfi "I" olan "noktasız i", yani "ı" (\u0131) karakterini içerir. Bu davranış Azerbaycan dili ("az") kültüründe de bulunur.

Bu nedenle, "i" harfini büyük harfe çevirme veya "I" harfini küçük harfe çevirme hakkında yapılan varsayımlar tüm kültürler arasında geçerli değildir. Eğer dize karşılaştırma yordamları için varsayılan aşırı yüklemeleri kullanırsanız, bunlar kültürler arasındaki farktan etkilenir. Karşılaştırılacak veriler dilsel değilse, aşağıdaki "bill" ve "BILL" dizelerinin büyük/küçük harfe duyarlı olmayan karşılaştırmasını gerçekleştirme girişimi gösterildiği gibi varsayılan aşırı yüklemelerin kullanılması istenmeyen sonuçlara neden olabilir.

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

Bu karşılaştırma, eğer kültür güvenliğe duyarlı yerlerde farkında olunmadan kullanılıyorsa aşağıdaki örnekteki gibi önemli sorunlara neden olabilir. IsFileURI("file:") gibi bir yöntem çağrısı, eğer geçerli kültür A.B.D. İngilizce ise true'i döndürürken, geçerli kültür Türkçe ise false'i döndürür. Bu nedenle, Türkçe sistemlerde "FILE:" ile başlayan büyük/küçük harfe duyarsız URI değerlerine olan erişimi engelleyen güvenlik önlemleri aşılabilir.

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

Bu durumda, "dosya:" dilsel olmayan, kültüre duyarsız bir tanımlayıcı olarak yorumlanması amaçlandığından, kod bunun yerine aşağıdaki örnekte gösterildiği gibi yazılmalıdır:

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

Sıralı dize işlemleri

Bir yöntem çağrısında StringComparison.Ordinal veya StringComparison.OrdinalIgnoreCase değerini belirtmek, doğal dillerin özelliklerinin göz ardı edildiği dilsel olmayan bir karşılaştırmayı belirtir. Bu StringComparison değerleriyle çağırılan yöntemler, temel dize işlemi kararlarında kültür tarafından parametre haline getirilen büyük/küçük harf veya eşitlik tabloları yerine basit bayt karşılaştırmalarını temel alır. Çoğu durumda, bu yaklaşım dizelerin istenen şekilde yorumlanmasının yanı sıra kodun daha hızlı ve daha güvenilir olmasını sağlar.

Sıralı karşılaştırmalar, her dizenin her baytının dilsel yorum olmadan karşılaştırıldığı dize karşılaştırmalarıdır; örneğin, "windows" "Windows" ile eşleşmiyor. Bu aslında C çalışma zamanı strcmp işlevine yapılan bir çağrıdır. Bağlam, dizelerin tam olarak karşılaşmasını gerektirdiğinde veya koruyucu bir eşleştirme ilkesine sahip olduğunda bu karşılaştırmayı kullanın. Ek olarak, sıralı karşılaştırma, sonuç oluştururken dilsel kurallar uygulamadığından en hızlı karşılaştırma işlemidir.

.NET'teki dizeler eklenmiş null karakterler (ve diğer yazdırılmayan karakterler) içerebilir. Sıralı ve kültüre duyarlı karşılaştırmalar (sabit kültür kullanan karşılaştırmalar dahil) arasındaki en belirgin farklardan biri bir dizedeki gömülü null karakterlerin işlenmesiyle ilgilidir. Bu karakterler, kültüre duyarlı karşılaştırmalar (sabit kültür kullanan karşılaştırmalar dahil) yapmak için String.Compare ve String.Equals yöntemlerini kullandığınızda yok sayılır. Sonuç olarak, eklenmiş null karakterler içeren dizeler, içermeyen dizelere eşit olarak kabul edilebilir. Yazdırılmayan ekli karakterler gibi String.StartsWithdize karşılaştırma yöntemleri için atlanabilir.

Önemli

Dize karşılaştırma yöntemleri gömülü null karakterleri yok saysa da, String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf ve String.StartsWith gibi dize arama yöntemleri bu karakterleri yok saymaz.

Aşağıdaki örnek, "A" ile "a" arasında birkaç katıştırılmış null karakter içeren benzer bir dize ile "Aa" dizesinin kültüre duyarlı bir karşılaştırmasını gerçekleştirir ve iki dizenin nasıl eşit kabul edildiğini gösterir:

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

Ancak, aşağıdaki örnekte gösterildiği gibi sıralı karşılaştırma kullandığınızda dizeler eşit kabul edilmez:

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

Büyük/küçük harfe duyarsız sıralı karşılaştırmalar bundan sonraki en koruyucu yaklaşımdır. Bu karşılaştırmalar, çoğu büyük/küçük harfi yok sayar; örneğin, "windows" ile "Windows" eşleşir. ASCII karakterleri ile çalışırken bu ilke StringComparison.Ordinal ile eşdeğerdir, ancak tek farkı genel ASCII büyük/küçük harflerini yok saymasıdır. Bu nedenle, [A, Z] (\u0041-\u005A) içindeki herhangi bir karakter [a,z] (\u0061-\007A) içindeki karşılık gelen karakterle eşleşir. ASCII aralığı dışındaki büyük/küçük harf kuralları sabit kültürün tablolarını kullanır. Bu nedenle, aşağıdaki karşılaştırma:

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

bu karşılaştırmaya eşdeğerdir (ancak daha hızlıdır):

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

Bu karşılaştırmalar yine de çok hızlıdır.

StringComparison.Ordinal ve StringComparison.OrdinalIgnoreCase'in her ikisi de doğrudan ikili değerleri kullanır ve eşleştirme için idealdir. Karşılaştırma ayarlarınızdan emin değilseniz bu iki değerden birini kullanın. Ancak, bayt bayt karşılaştırması yaptıkları için, dilsel sıralama düzenine (İngilizce sözlük gibi) göre değil, ikili sıralama düzenine göre sıralarlar. Sonuçlar kullanıcılara görüntülenirse çoğu bağlamda tuhaf görünebilir.

Sıralı semantik, bağımsız değişken içermeyen StringComparison aşırı yüklemeler için String.Equals varsayılan değerdir (eşitlik işleci dahil). Her durumda StringComparison parametresi olan bir aşırı yüklemeyi çağırmanızı öneririz.

Sabit kültürü kullanan dize işlemleri

Sabit kültürle yapılan karşılaştırmalar, statik CompareInfo özelliği tarafından döndürülen CultureInfo.InvariantCulture özelliğini kullanır. Bu davranış tüm sistemlerde aynıdır; aralığı dışındaki karakterleri eşdeğer sabit karakter olarak düşündüğü karakterlere çevirir. Bu ilke, kültürler arası tek bir dize davranışı bulundurmak için kullanışlı olabilir, ancak genellikle beklenmeyen sonuçlar sağlar.

Sabit kültürle yapılan büyük/küçük harfe duyarsız karşılaştırmalar, karşılaştırma bilgisi için statik CompareInfo özelliğinden döndürülen statik CultureInfo.InvariantCulture özelliğini kullanır. Bu çevrilen karakterler arasındaki tüm büyük/küçük harf farkları yok sayılır.

StringComparison.InvariantCulture ve StringComparison.Ordinal kullanan karşılaştırmalar ASCII dizelerinde de aynı şekilde çalışır. Ancak StringComparison.InvariantCulture, bir bayt kümesi olarak yorumlanması gereken dizeler için uygun olmayabilecek dilsel kararlar alır. CultureInfo.InvariantCulture.CompareInfo nesnesi Compare yönteminin belirli karakter kümelerini eşdeğer olarak yorumlamasını sağlar. Örneğin, aşağıdaki eşitlik sabit kültürde geçerlidir:

InvariantCulture: a + ̊ = ş

LATIN KÜÇÜK HARF A karakteri "a" (\u0061), "+ " ̊" (\u030a) BİRLEŞTİRİ HALKASI karakterinin yanında olduğunda, LATIN KÜÇÜK HARF A VE ÜSTÜNDE HALKA karakteri "å" (\u00e5) olarak yorumlanır. Aşağıdaki örnekte gösterildiği gibi bu davranış, sıralı karşılaştırmadan farklıdır.

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

Dosya adlarını, tanımlama bilgilerini veya "å" gibi bir bileşimin görüntülenebildiği başka bir şeyi yorumlarken sıralı karşılaştırmalar yine de en saydam ve en uygun davranışı sunar.

Dengede, sabit kültürün karşılaştırma için yararlı hale getiren birkaç özelliği vardır. Dilsel olarak ilgili bir şekilde karşılaştırma yapar ve bu da tam sembolik denkliği garanti etmesini önler, ancak herhangi bir kültürde görüntüleme seçeneği değildir. Karşılaştırma için StringComparison.InvariantCulture kullanmanın birkaç nedeninden biri, kültürler arası aynı görüntülenmesi için sıralanan verilerin kalıcı hale getirilmesidir. Örneğin bir uygulamanın yanında, görüntülenecek bir sıralı tanımlayıcılar listesi içeren büyük bir veri dosyası varsa bu listeye ekleme yapmak sabit stil sıralama ile eklemeyi gerektirir.

Yöntem çağrınız için StringComparison üyesi seçme

Aşağıdaki tabloda, anlamsal dize bağlamından bir StringComparison numaralandırma üyesine eşleme özetlenmiştir:

Veri Davranış İlgili System.StringComparison

değer
Büyük/küçük harfe duyarlı dahili tanımlayıcılar.

XML ve HTTP gibi standartlardaki büyük/küçük harfe duyarlı tanımlayıcılar.

Güvenlikle ilgili büyük/küçük harfe duyarlı ayarlar.
Baytların tam olarak eşleştiği dilsel olmayan bir tanımlayıcı. Ordinal
Büyük/küçük harfe duyarsız iç tanımlayıcılar.

XML ve HTTP gibi standartlardaki büyük/küçük harfe duyarsız tanımlayıcılar.

Dosya yolları.

Kayıt defteri anahtarları ve değerleri.

Ortam değişkenleri.

Kaynak tanımlayıcıları (örneğin, işleyici adları).

Güvenlikle ilgili büyük/küçük harfe duyarsız ayarlar.
Büyük/küçük harfle ilgisiz olan dilsel olmayan bir tanımlayıcı. OrdinalIgnoreCase
Kalıcı olan bazı dilsel veriler.

Sabit bir sıralama düzeni gerektiren dilsel verinin görüntülenmesi.
Dilsel olan ancak kültüre duyarlı olmayan veri. InvariantCulture

-veya-

InvariantCultureIgnoreCase
Kullanıcıya görüntülenen veri.

Çoğu kullanıcı girişi.
Yerel dilsel özellikleri gerektiren veriler. CurrentCulture

-veya-

CurrentCultureIgnoreCase

.NET'te yaygın dize karşılaştırma yöntemleri

Aşağıdaki bölümlerde, en yaygın olarak dize karşılaştırmaları için kullanılan yöntemler açıklanır.

String.Compare

Varsayılan yorum: StringComparison.CurrentCulture.

Dize yorumlamak için en önemli işlem olarak bu yöntem çağrılarının tüm örnekleri, dizelerin geçerli kültüre göre mi yoksa geçerli kültürden bağımsız (simgesel) olarak mı yorumlanacağını belirlemek için incelenmelidir. Genellikle bu ikinci seçenektir ve bunun yerine bir StringComparison.Ordinal karşılaştırma kullanılmalıdır.

System.Globalization.CompareInfo özelliği tarafından döndürülen CultureInfo.CompareInfo sınıfı, aynı zamanda Compare bayrak sabit listesini kullanarak çok sayıda eşleştirme seçeneği (sıralı, boşluğu göz ardı ederek, kana türünü göz ardı ederek vb.) sunan bir CompareOptions yöntemi de içerir.

String.CompareTo

Varsayılan yorum: StringComparison.CurrentCulture.

Bu yöntem şu anda bir tür belirten bir aşırı yükleme sunmaz StringComparison . Genellikle bu yöntemi önerilen String.Compare(String, String, StringComparison) forma dönüştürmek mümkündür.

IComparable ve IComparable<T> arabirimlerini uygulayan türler bu yöntemi uygular. Bir parametre seçeneği StringComparison sunmadığından, türlerin uygulanması genellikle kullanıcının oluşturucusunda bir StringComparer belirtmesine olanak tanır. Aşağıdaki örnek, sınıf oluşturucusu bir FileName parametresi içeren bir StringComparer sınıfını tanımlar. Bu StringComparer nesnesi daha sonra FileName.CompareTo yönteminde kullanılır.

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

Varsayılan yorum: StringComparison.Ordinal.

String sınıfı, statik veya örnek Equals yöntemi aşırı yüklemelerini çağırarak veya statik eşitlik işlecini kullanarak eşitliği test etmenizi sağlar. Aşırı yüklemeler ve işleç, varsayılan olarak sıralı karşılaştırma kullanır. Ancak, sıralı karşılaştırma yapmak isteseniz bile StringComparison türünü açıkça belirten bir aşırı yükleme çağırmanızı öneririz, çünkü bu, belirli bir dize yorumu için kodda arama yapmanızı kolaylaştırır.

String.ToUpper ve String.ToLower

Varsayılan yorum: StringComparison.CurrentCulture.

ve String.ToLower() yöntemlerini kullanırken String.ToUpper() dikkatli olun çünkü bir dizeyi büyük veya küçük harfe zorlamak genellikle büyük/küçük harfe bakılmaksızın dizeleri karşılaştırmak için küçük bir normalleştirme olarak kullanılır. Bu durumda, büyük/küçük harfe duyarsız bir karşılaştırma kullanmayı düşünün.

Aynı zamanda String.ToUpperInvariant ve String.ToLowerInvariant yöntemleri de mevcuttur. ToUpperInvariant, büyük/küçük harfi normalleştirmek için standart yöntemdir. StringComparison.OrdinalIgnoreCase kullanılarak yapılan karşılaştırmalar, davranışsal olarak iki çağrının birleşimidir: her iki dize bağımsız değişkeninde ToUpperInvariant'i çağırmak ve StringComparison.Ordinal kullanarak bir karşılaştırma yapmak.

Aşırı yüklemeler aynı zamanda, yöntemde kültürü temsil eden bir CultureInfo nesnesini geçirerek belirli bir kültürde büyük harfe ve küçük harfe dönüştürme için de kullanılabilir.

Char.ToUpper ve Char.ToLower

Varsayılan yorum: StringComparison.CurrentCulture.

ve Char.ToLower(Char) yöntemleri, Char.ToUpper(Char) önceki bölümde açıklanan ve String.ToLower() yöntemlerine benzer şekilde String.ToUpper() çalışır.

String.StartsWith ve String.EndsWith

Varsayılan yorum: StringComparison.CurrentCulture.

Varsayılan olarak, bu yöntemlerin ikisi de kültüre duyarlı bir karşılaştırma yapar. Özellikle, yazdırılmayan karakterleri yoksayabilirler.

String.IndexOf ve String.LastIndexOf

Varsayılan yorum: StringComparison.CurrentCulture.

Bu yöntemlerin varsayılan aşırı yüklemelerinin karşılaştırmaları gerçekleştirmesinde tutarlılık eksikliği vardır. Bir String.IndexOf parametresi içeren tüm String.LastIndexOf ve Char yöntemleri sıralı karşılaştırma yapar, ancak bir String.IndexOf parametresi içeren varsayılan String.LastIndexOf ve String yöntemleri kültüre duyarlı bir karşılaştırma yapar.

Eğer String.IndexOf(String) veya String.LastIndexOf(String) yöntemini çağırıp geçerli örnekte konumlandırılacak bir dizeye geçirirseniz, StringComparison türünü açıkça belirten bir aşırı yüklemeyi çağırmanızı öneririz. Bağımsız değişken içeren Char aşırı yüklemeler bir StringComparison tür belirtmenize izin vermez.

Dolaylı olarak dize karşılaştırması gerçekleştiren yöntemler

Merkezi işlem olarak dize karşılaştırmasına sahip olan dize olmayan bazı yöntemler StringComparer türünü kullanır. StringComparer sınıfı, StringComparer yöntemleri aşağıdaki türde dize karşılaştırması yapan StringComparer.Compare örneklerini döndüren altı statik özelliği içerir:

Array.Sort ve Array.BinarySearch

Varsayılan yorum: StringComparison.CurrentCulture.

Herhangi bir veriyi bir koleksiyonda depoladığınızda veya bir dosyadan veya veritabanından kalıcı hale getirilmiş veriyi bir koleksiyonun içine okuduğunuzda geçerli kültürün değiştirilmesi, koleksiyon içindeki sabitleri geçersiz kılabilir. Array.BinarySearch yöntemi, aranacak dizideki öğelerin zaten sıralı olduğunu varsayar. Array.Sort yöntemi, dizideki herhangi bir dize öğesini sıralamak için, ayrı ayrı öğeleri düzenlemek amacıyla String.Compare yöntemini çağırır. Kültüre duyarlı bir karşılaştırıcı kullanmak, eğer dizi sıralanana ve içerikleri aranana kadar geçen zaman arasında kültür değişirse tehlikeli olabilir. Örneğin aşağıdaki kodda depolama ve alma işlemleri, Thread.CurrentThread.CurrentCulture özelliği tarafından dolaylı olarak sağlanan karşılaştırıcıda gerçekleşir. Eğer kültür StoreNames ve DoesNameExist arasında değişebilirse ve özellikle dizi içerikleri iki yöntem çağrısı arasında bir yerde kalıcı hale getirilirse, ikili arama başarısız olabilir.

// 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

Aşağıdaki örnekte, diziyi sıralamak ve aramak için aynı sıralı (kültüre duyarsız) karşılaştırma yöntemini kullanan önerilen bir yöntem görünür. Değişim kodu, iki örnekte Line A ve Line B olarak etiketlenen satırlarda yansıtılır.

// 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

Eğer bu veriler kalıcı hale getirilirse ve kültürler arasında taşınırsa, ve kullanıcıya bu verileri sunmak için sıralama kullanılırsa, dilsel olarak daha iyi kullanıcı çıktısı için çalışan, ancak kültür değişikliklerinden etkilenmeyen StringComparison.InvariantCulture öğesini kullanmayı düşünebilirsiniz. Aşağıdaki örnek, diziyi sıralamak ve aramak üzere sabit kültürü kullanmak için önceki iki örneği değiştirir.

// 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

Koleksiyonlar örneği: Hashtable oluşturucu

Karma dizeleri, dizelerin karşılaştırılma şeklinden etkilenen bir işlemin ikinci bir örneğini sağlar.

Aşağıdaki örnek, Hashtable özelliği tarafından döndürülen StringComparer nesnesini geçirerek bir StringComparer.OrdinalIgnoreCase nesnesi oluşturur. StringComparer'dan türetilen bir StringComparer sınıfı IEqualityComparer arabirimini uyguladığından GetHashCode yöntemi, karma tablosundaki dizelerin karma kodunu hesaplamak için kullanılır.

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

Ayrıca bkz.