Aracılığıyla paylaş


.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 dizeler genellikle tüm kültürlerde aynı şekilde işlenmelidir. XML etiketleri, HTML etiketleri, kullanıcı adları, dosya yolları ve sistem nesnelerinin adları gibi kültürel açıdan bağımsız dize verileri kültüre duyarlıymış gibi yorumlandığında, uygulama kodu ince hatalara, düşük performansa ve bazı durumlarda güvenlik sorunlarına tabi olabilir.

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.

Tavsiye

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 kaçınmanız gereken aşağıdaki uygulamalardan:

  • 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.
  • Aşırı yüklenmiş String.Compare veya CompareTo yöntemlerini kullanmayın ve iki dizenin 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 etmemektedir ve bunun yerine dizelerin karşılaştırılacağı veya değiştirileceği kesin yöntemi tanımlar. Varsayılanlara dayanmayan yöntemlerin çoğu, dize karşılaştırmasını kültüre ve büyük/küçük harfe göre açıkça belirten bir numaralandırma türünde StringComparison parametresi içerir. Aşağıdaki tabloda numaralandırma üyeleri açıklanmaktadır StringComparison .

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 duyarlı olmayan 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 duyarsız sayısal karşılaştırma gerçekleştirir.

Örneğin, IndexOf bir karakter veya dizeyle eşleşen bir nesnedeki bir String alt dizenin dizinini döndüren 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 Char arayanlar) ordinal bir karşılaştırma gerçekleştirirken, diğerleri (dize örneğinde bir dizeyi 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ı aşağıdaki örnekte, geliştiricinin aslında iki dizenin sıralı mı yoksa dilsel mi karşılaştırmasını amaçladığını veya url.Scheme ile "https" arasında büyük/küçük harf farkının eşitlik testinde false döndürmesine neden olup olmayacağı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 da kodun daha okunabilir olmasını ve hata ayıklamasını ve bakımının daha kolay olmasını sağlar. Aşağıdaki örnekte, önceki örnekle ilgili olarak sorulan sorular ele alınıyor. Sıralama karşılaştırmasının kullanıldığını ve büyük/küçük harf farklarının göz ardı edildiğini açıkça gösterir.

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ırması, özellikle eşitlik için sıralama ve test etme olmak üzere dizeyle ilgili birçok işlemin kalbidir. Dizeler belirli bir düzende sıralanır: Eğer "my" kelimesi sıralanmış bir metin listesinde "string" kelimesinden önce geliyorsa, "my" değeri "string" ile eşit veya daha küçük olarak karşılaştırılmalıdır. Buna ek olarak, karşılaştırma örtük olarak eşitliği tanımlar. Karşılaştırma işlemi, eşit gördüğü dizeler için sıfır döndürür. İyi bir yorum, hiçbir dizenin diğerinden küçük olmamasıdır. Dizelerle ilgili en anlamlı işlemler şu yordamlardan birini veya her ikisini de içerir: başka bir dizeyle karşılaştırma ve iyi tanımlanmış bir sıralama işlemi yürütme.

Uyarı

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, ordinal olan veya geçerli kültürün ya da İngilizce dilini temel alan yerel ayardan bağımsız bir kültür olan sabit kültürün büyük/küçük harf farkı 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ı

Bir ölçüt, dizeleri karşılaştırırken geçerli kültürün kurallarını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 ayarını kullanır. Kültür kullanıcı tarafından ayarlanmadıysa, varsayılan olarak işletim sisteminin ayarına ayarlanır. Veriler dilsel olarak ilgili 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 bir kültür değiştiğinde .NET'teki karşılaştırma ve harf büyüklüğü davranışı değişir. Uygulama, uygulamanın geliştirildiği bilgisayardan farklı bir kültüre sahip bir bilgisayarda yürütürse veya yürütülen iş parçacığı kendi kültürünü değiştirdiğinde bu durum ortaya çıkar. Bu davranış kasıtlıdır, ancak birçok geliştirici için belirgin değildir. Aşağıdaki örnekte, ABD İngilizcesi ("en-US") ile İsveççe ("sv-SE") kültürleri arasındaki sıralama düzeni farklılıkları gösterilmektedir. "å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 büyük/küçük harfe duyarsız karşılaştırmalar, iş parçacığının geçerli kültürü tarafından dikte edilen büyük/küçük harf kullanımını yoksaymaları dışında kültüre duyarlı karşılaştırmalarla aynıdır. Bu davranış, sıralama düzenlerinde de kendini gösterebilir.

Geçerli kültür semantiğini kullanan karşılaştırmalar aşağıdaki yöntemler için varsayılandır:

Her durumda, yöntem çağrısının amacını net hale getirmek için bir StringComparison parametresine sahip bir aşırı yüklemeyi çağırmanızı öneririz.

Dilsel olmayan dize verileri dilsel olarak yorumlandığında veya belirli bir kültüre ait dize verileri başka bir kültürün gelenekleri kullanılarak yorumlandığında ince ve bu kadar ince olmayan hatalar ortaya çıkar. En iyi bilinen örnek Turkish-I sorunudur.

ABD İngilizcesi de dahil olmak üzere neredeyse tüm Latin alfabelerinde "i" (\u0069) karakteri, "I" karakterinin küçük harfli sürümüdür (\u0049). Bu büyük/küçük harf kuralı, böyle bir kültürde programlayan biri için hızlı bir şekilde varsayılan hale gelir. Ancak, Türkçe ("tr-TR") alfabesinde "I with a dot" karakteri "İ" (\u0130) bulunur ve bu da "i" harfinin büyük versiyonudur. Türkçede, "ı" harfi, "I" olarak büyük harfe dönüşen noktasız küçük "i" karakterini de içerir (\u0131). Bu davranış Azerbaycan ("az") kültüründe de meydana gelir.

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. Dize karşılaştırma yordamları için varsayılan aşırı yüklemeleri kullanırsanız, bunlar kültürler arasında varyansa tabi olur. Karşılaştırılacak veriler dilsel değilse, yani dil ile ilgili değilse, "bill" ve "BILL" dizelerinin büyük/küçük harf duyarsız karşılaştırmasını gerçekleştirmeye çalışırken varsayılan aşırı yüklemelerin kullanılması, istenmeyen sonuçlara yol açabilir.

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, kültür aşağıdaki örnekte olduğu gibi güvenlik açısından hassas ayarlarda yanlışlıkla kullanılıyorsa önemli sorunlara neden olabilir. gibi IsFileURI("file:") bir yöntem çağrısı, geçerli kültürün ABD İngilizcesi olup olmadığını, ancak true geçerli kültürün Türkçe olup olmadığını döndürürfalse. Böylece, Türk sistemlerinde birisi "FILE:" ile başlayan büyük/küçük harfe duyarlı olmayan URI'lere erişimi engelleyen güvenlik önlemlerini atlatabilir.

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ğerinin belirtilmesi, doğal dillerin özelliklerinin göz ardı edildiği dilsel olmayan bir karşılaştırmayı ifade eder. Bu StringComparison değerlerle çağrılan yöntemler, dize işlemi kararlarını kültüre göre parametreleştirilmiş büyük/küçük harf veya denklik tabloları yerine basit bayt karşılaştırmalarına dayandırır. Çoğu durumda bu yaklaşım, kodu daha hızlı ve daha güvenilir hale getirirken dizelerin hedeflenen yorumuna en uygun olanıdır.

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 temelde C çalışma zamanı strcmp işlevine yapılan bir çağrıdır. Bağlam dizelerin tam olarak eşleşmesini gerektiriyorsa veya muhafazakar eşleştirme ilkesi gerektiriyorsa bu karşılaştırmayı kullanın. Buna ek olarak, sıralı karşılaştırma en hızlı karşılaştırma işlemidir çünkü bir sonuç belirlerken dil kuralları uygulamaz.

.NET'teki dizeler eklenmiş null karakterler (ve diğer yazdırılmayan karakterler) içerebilir. Sıra ve kültüre duyarlı karşılaştırma (sabit kültürü kullanan karşılaştırmalar dahil) arasındaki en net farklardan biri, bir dizedeki katıştırılmış null karakterlerin işlenmesiyle ilgilidir. Kültüre duyarlı karşılaştırmalar (değişmez kültürü kullanan karşılaştırmalar dahil) gerçekleştirmek için String.Compare ve String.Equals yöntemlerini kullandığınızda bu karakterler yoksayı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 eklenmiş null karakterleri dikkate almasa da, , String.Contains, String.EndsWithve String.IndexOfString.LastIndexOfgibi String.StartsWithdize arama yöntemleri bunu yapmaz.

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 bir sonraki en muhafazakar yaklaşımdır. Bu karşılaştırmalar, çoğu büyük/küçük harf farkını görmezden gelir; örneğin, "windows" "Windows" ile eşleşir. ASCII karakterleriyle ilgilenirken, bu ilke ile eşdeğerdir StringComparison.Ordinal, ancak normal ASCII büyük/küçük harflerini yoksayar. Bu nedenle, [A, Z] (\u0041-\u005A) içindeki tüm karakterler [a,z] (\u0061-\007A) içindeki ilgili karakterle eşleşir. ASCII aralığının dışındaki harf büyüklüğü, değişmez 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 hala çok hızlıdır.

Hem StringComparison.Ordinal hem de StringComparison.OrdinalIgnoreCase ikili değerleri doğrudan kullanır ve eşleştirme için en uygun olanlarıdır. 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. Kullanıcılara görüntülenirse çoğu bağlamda sonuçlar garip görünebilir.

Ordinal semantik, String.Equals bağımsız değişkeni içermeyen StringComparison aşırı yüklemeler (eşitlik işleci dahil) için varsayılandır. Her durumda, parametresi olan StringComparison 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ığının dışındaki karakterleri eşdeğer sabit karakterler olduğuna inandığı değere çevirir. Bu ilke, kültürler arasında bir metin dizisi davranışını korumak için yararlı olabilir, ancak sıklıkla beklenmedik sonuçlar verir.

Değişmez kültürle yapılan büyük/küçük harfe duyarsız karşılaştırmalar için karşılaştırma bilgilerini almak amacıyla CompareInfo ve CultureInfo.InvariantCulture statik özellikleri de kullanılır. Bu çevrilen karakterler arasındaki tüm büyük/küçük harf farkları yoksayılır.

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

InvariantCulture: a + ̊ = ş

LATIN KÜÇÜK HARF A karakteri "a" (\u0061), "+ " ̊" (\u030a) ÜST HALKA İŞARETİ karakterinin yanında olduğunda, ÜST HALKA İŞARETLİ LATIN KÜÇÜK HARF A 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 {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

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.

Genel olarak, sabit kültürün karşılaştırma için yararlı kılan pek az özelliği vardır. Dil açısından ilgili şekilde karşılaştırma yapar, bu da tam sembolik denkliği garanti etmesini önler, ancak hiçbir kültürde sergileme tercihi değildir. Karşılaştırma için kullanmanın StringComparison.InvariantCulture birkaç nedenden biri, sıralı verileri kültürler arası özdeş bir görüntü için kalıcı hale getirmektir. Örneğin, görüntüleme için sıralanmış tanımlayıcıların bir listesini içeren büyük bir veri dosyası bir uygulamaya eşlik ettiğinde, bu listeye ekleme yapmak değişmez tarzda sıralama ile ekleme 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ı iç tanımlayıcılar.

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

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

XML ve HTTP gibi standartlarda harf büyüklüğüne duyarlı olmayan tanımlayıcılar.

Dosya yolları.

Kayıt defteri anahtarları ve değerleri.

Ortam değişkenleri.

Kaynak tanımlayıcıları (örneğin, tanıtıcı adları).

Büyük/küçük harfe duyarlı olmayan güvenlikle ilgili ayarlar.
Büyük/küçük harfle ilgisiz olan dilsel olmayan bir tanımlayıcı. OrdinalIgnoreCase
Bazı kalıcı, dilsel olarak ilgili veriler.

Sabit bir sıralama düzeni gerektiren dilsel verilerin görüntülenmesi.
Kültürel açıdan herhangi bir tarafa eğilim göstermeyen, ancak dilsel açıdan ilgili veriler. InvariantCulture

-veya-

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

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

-veya-

CurrentCultureIgnoreCase

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

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

String.Compare

Varsayılan yorum: StringComparison.CurrentCulture.

Dize yorumlamanın en merkezi işlemi olan bu yöntem çağrılarının tüm örnekleri, dizelerin geçerli kültüre göre yorumlanıp yorumlanmayacağını veya kültürle (sembolik olarak) ilişkilendirilip ilişkilendirilmeyeceğini 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ı, Compare bayrak enumerasyonu aracılığıyla çok sayıda eşleştirme seçeneği (sıralı, boşluk yoksayma, kana türünü yoksayma vb.) sağlayan bir CompareOptions yöntem de içerir.

String.CompareTo

Varsayılan yorum: StringComparison.CurrentCulture.

Bu yöntem şu anda bir StringComparison türünü belirten bir yükleme seçeneği sunmaz. 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. StringComparison parametresini sunmadığı için, tiplerin uygulanması genellikle kullanıcının oluşturucusunda bir StringComparer belirtmesine olanak tanır. Aşağıdaki örnek, sınıf kurucusunun bir FileName parametresini içeren bir StringComparer sınıfı tanımlar. Bu StringComparer nesne 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.

sınıfı, String 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ırmayı kullanır. Ancak, ordinal bir karşılaştırma yapmak isteseniz bile türü açıkça belirten StringComparison bir aşırı yüklemeyi çağırmanızı öneririz; bu, belirli bir dize yorumlamasını kodda bulmanızı kolaylaştırır.

String.ToUpper ve String.ToLower

Varsayılan yorum: StringComparison.CurrentCulture.

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

String.ToUpperInvariant ve String.ToLowerInvariant yöntemleri de kullanılabilir. ToUpperInvariant büyük/küçük harf normalleştirmenin standart yoludur. Karşılaştırmalar StringComparison.OrdinalIgnoreCase kullanılarak yapıldığında, davranışsal olarak iki çağrının bileşimidir: her iki dize bağımsız değişkeninde ToUpperInvariant çağırmak ve StringComparison.Ordinal kullanarak karşılaştırma yapmak.

Aşırı yüklemeler, yöntemine bu kültürü temsil eden bir CultureInfo nesne geçirilerek belirli bir kültürde büyük ve küçük harfe dönüştürülebilir.

Char.ToUpper ve Char.ToLower

Varsayılan yorum: StringComparison.CurrentCulture.

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

String.StartsWith ve String.EndsWith

Varsayılan yorum: StringComparison.CurrentCulture.

Varsayılan olarak, bu yöntemlerin her ikisi de kültüre duyarlı bir karşılaştırma gerçekleştirir. Ö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. Parametre içeren tüm String.IndexOf ve String.LastIndexOf yöntemler sıralı bir karşılaştırma gerçekleştirir, ancak varsayılan Char ve String.IndexOf parametre içeren String.LastIndexOf yöntemler kültüre duyarlı String bir karşılaştırma gerçekleştirir.

String.IndexOf(String) veya String.LastIndexOf(String) yöntemini çağırır ve geçerli örnekte bulmak için bir dize geçirirseniz, türü açıkça belirten bir StringComparison aşırı yüklemesini çağırmanızı öneririz. Char bağımsız değişkeni içeren aşırı yüklemeler, bir StringComparison türü belirtmenize izin vermez.

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

Merkezi bir işlem olarak dize karşılaştırması olan bazı dize dışı yöntemler türünü kullanır StringComparer . StringComparer sınıfı, StringComparer örneklerini döndüren altı statik özellik içerir; bu örneklerin StringComparer.Compare yöntemleri, aşağıdaki dize karşılaştırması türlerini gerçekleştirir:

Array.Sort ve Array.BinarySearch metodları

Varsayılan yorum: StringComparison.CurrentCulture.

Bir koleksiyonda herhangi bir veri depoladığınızda veya bir dosyadan veya veritabanından kalıcı verileri koleksiyona okuduğunuzda, geçerli kültürün değiştirilmesi koleksiyondaki sabitleri geçersiz kılabilir. yöntemi, Array.BinarySearch aranacak dizideki öğelerin zaten sıralandığını varsayar. Dizideki herhangi bir dize öğesini sıralamak için Array.Sort yöntemi, tek tek öğeleri sıralamak için String.Compare yöntemini çağırır. Kültüre duyarlı bir karşılaştırıcı kullanmak, dizinin sıralandığı ve içindekilerin aranacağı zaman arasında kültür değiştiğinde tehlikeli olabilir. Örneğin, aşağıdaki kodda depolama ve alma işlemi, Thread.CurrentThread.CurrentCulture özelliği tarafından dolaylı yoldan sağlanan karşılaştırıcı üzerinde çalışır. Kültür, StoreNames ve DoesNameExist çağrıları arasında değişebilir ve özellikle dizi içeriği iki yöntem çağrısı arasında bir yerde kalıcı hale getirilmişse, 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 varyasyon gösterilir. Değişiklik kodu, iki örnekte Line A ve Line B olarak etiketlenmiş satırlara 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

Bu veriler kalıcı hale gelirse ve kültürler arasında taşınırsa ve bu verileri kullanıcıya sunmak için sıralama kullanılırsa, daha iyi kullanıcı çıktısı için dilsel olarak çalışan ancak kültürdeki değişikliklerden etkilenmeyen öğesini kullanmayı StringComparison.InvariantCulturedüşünebilirsiniz. Aşağıdaki örnek, diziyi sıralamak ve aramak için sabit kültürü kullanmak üzere ö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

Hashleme işlemi, dizelerin karşılaştırılma şeklinin etkilediği bir işlemin ikinci örneğini gösterir.

Aşağıdaki örnek, Hashtable özelliği tarafından döndürülen StringComparer nesnesini geçirerek bir StringComparer.OrdinalIgnoreCase nesnesinin örneğini oluşturur. Bir sınıf `StringComparer`'den türetildiği ve `StringComparer` arabirimini uyguladığı için, `c3` yöntemi, karma tablosundaki dizelerin hash 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 bakınız