.NET'te normal ifadeler için en iyi yöntemler

.NET'teki normal ifade altyapısı, metinleri karşılaştırmak ve değişmez metin eşleştirmek yerine desen eşleşmelerine göre işleyen güçlü, tam özellikli bir araçtır. Çoğu durumda desen eşleme işlemini hızlı ve verimli şekilde yapar. Ancak bazı durumlarda normal ifade altyapısı yavaş görünebilir. Aşırı durumlarda saatler ve hatta günler boyunca görece küçük bir girişi işlerken yanıt vermeyi durdurmuş gibi bile görünebilir.

Bu makalede, geliştiricilerin normal ifadelerinin en iyi performansa ulaşmasını sağlamak için benimseyebileceği en iyi uygulamalardan bazıları özetlenmiştir.

Uyarı

Güvenilmeyen girişi işlemek için kullanırken System.Text.RegularExpressions bir zaman aşımı geçirin. Kötü amaçlı bir kullanıcı için giriş RegularExpressionssağlayabilir ve bu da Hizmet Reddi saldırısına neden olabilir. ASP.NET Zaman aşımı kullanan RegularExpressions Core framework API'leri.

Giriş kaynağını göz önünde bulundurun

Genelde normal ifadeler iki tür giriş kabul edebilir: sınırlandırılmış ya da sınırlandırılmamış. Kısıtlanmış giriş, bilinen veya güvenilir bir kaynaktan gelen ve önceden tanımlanmış bir biçimi izleyen bir metindir. Kısıtlanmamış giriş, web kullanıcısı gibi güvenilir olmayan bir kaynaktan gelen ve önceden tanımlanmış veya beklenen bir biçimi izlemeyebilen bir metindir.

Normal ifade desenleri genellikle geçerli girişle eşleşecek şekilde yazılır. Diğer bir deyişle, geliştiriciler eşleştirmek istedikleri metni inceler ve bu metinle eşleşen normal bir ifade deseni yazarlar. Geliştiriciler daha sonra bu desenin düzeltme ya da daha fazla ayrıntı gerektirip gerektirmediğini, birden çok geçerli giriş öğesini test ederek belirler. Desen tüm varsayılan geçerli girişlerle eşleştiğinde üretime hazır olduğu bildirilir ve serbest bırakılmış bir uygulamaya dahil edilebilir. Bu yaklaşım, normal ifade desenini kısıtlanmış girişi eşleştirmek için uygun hale getirir. Ancak, kısıtlanmamış girişi eşleştirmek için uygun hale getirmez.

Kısıtlanmamış girişi eşleştirmek için normal ifadenin üç tür metni verimli bir şekilde işlemesi gerekir:

  • Normal ifade deseniyle eşleşen metin.
  • Normal ifade deseni ile eşleşmeyen metin.
  • Normal ifade deseniyle neredeyse eşleşen metin.

Son metin türü, sınırlandırılmış girdi işlemek üzere yazılmış bir normal ifade için özellikle sorunludur. Bu normal ifade kapsamlı geri izlemeyi de kullanıyorsa, normal ifade altyapısı zararsız görünen metinleri işlemek için normal olmayan bir süre (bazı durumlarda, birkaç saat veya gün) geçirebilir.

Uyarı

Aşağıdaki örnek, aşırı geri göndermeye eğilimli ve geçerli e-posta adreslerini reddetme olasılığı olan normal bir ifade kullanır. Bunu bir e-posta doğrulama yordamında kullanmamalısınız. E-posta adreslerini doğrulayan normal bir ifade istiyorsanız bkz . Nasıl yapılır: Dizelerin Geçerli E-posta Biçiminde Olduğunu Doğrulama.

Örneğin, bir e-posta adresinin diğer adını doğrulamak için yaygın olarak kullanılan ancak sorunlu bir normal ifade düşünün. Normal ifade ^[0-9A-Z]([-.\w]*[0-9A-Z])*$ , geçerli bir e-posta adresi olarak kabul edilenleri işlemek için yazılır. Geçerli bir e-posta adresi alfasayısal karakterden ve ardından alfasayısal, nokta veya kısa çizgi olabilecek sıfır veya daha fazla karakterden oluşur. Normal ifade, alfasayısal bir karakterle bitmelidir. Ancak, aşağıdaki örnekte gösterildiği gibi, bu normal ifade geçerli girişi kolayca işlese de, neredeyse geçerli girişi işlerken performansı verimsizdir:

using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

public class DesignExample
{
    public static void Main()
    {
        Stopwatch sw;
        string[] addresses = { "AAAAAAAAAAA@contoso.com",
                             "AAAAAAAAAAaaaaaaaaaa!@contoso.com" };
        // The following regular expression should not actually be used to
        // validate an email address.
        string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])*$";
        string input;

        foreach (var address in addresses)
        {
            string mailBox = address.Substring(0, address.IndexOf("@"));
            int index = 0;
            for (int ctr = mailBox.Length - 1; ctr >= 0; ctr--)
            {
                index++;

                input = mailBox.Substring(ctr, index);
                sw = Stopwatch.StartNew();
                Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
                sw.Stop();
                if (m.Success)
                    Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
                                      index, m.Value, sw.Elapsed);
                else
                    Console.WriteLine("{0,2}. Failed  '{1,25}' in {2}",
                                      index, input, sw.Elapsed);
            }
            Console.WriteLine();
        }
    }
}

// The example displays output similar to the following:
//     1. Matched '                        A' in 00:00:00.0007122
//     2. Matched '                       AA' in 00:00:00.0000282
//     3. Matched '                      AAA' in 00:00:00.0000042
//     4. Matched '                     AAAA' in 00:00:00.0000038
//     5. Matched '                    AAAAA' in 00:00:00.0000042
//     6. Matched '                   AAAAAA' in 00:00:00.0000042
//     7. Matched '                  AAAAAAA' in 00:00:00.0000042
//     8. Matched '                 AAAAAAAA' in 00:00:00.0000087
//     9. Matched '                AAAAAAAAA' in 00:00:00.0000045
//    10. Matched '               AAAAAAAAAA' in 00:00:00.0000045
//    11. Matched '              AAAAAAAAAAA' in 00:00:00.0000045
//
//     1. Failed  '                        !' in 00:00:00.0000447
//     2. Failed  '                       a!' in 00:00:00.0000071
//     3. Failed  '                      aa!' in 00:00:00.0000071
//     4. Failed  '                     aaa!' in 00:00:00.0000061
//     5. Failed  '                    aaaa!' in 00:00:00.0000081
//     6. Failed  '                   aaaaa!' in 00:00:00.0000126
//     7. Failed  '                  aaaaaa!' in 00:00:00.0000359
//     8. Failed  '                 aaaaaaa!' in 00:00:00.0000414
//     9. Failed  '                aaaaaaaa!' in 00:00:00.0000758
//    10. Failed  '               aaaaaaaaa!' in 00:00:00.0001462
//    11. Failed  '              aaaaaaaaaa!' in 00:00:00.0002885
//    12. Failed  '             Aaaaaaaaaaa!' in 00:00:00.0005780
//    13. Failed  '            AAaaaaaaaaaa!' in 00:00:00.0011628
//    14. Failed  '           AAAaaaaaaaaaa!' in 00:00:00.0022851
//    15. Failed  '          AAAAaaaaaaaaaa!' in 00:00:00.0045864
//    16. Failed  '         AAAAAaaaaaaaaaa!' in 00:00:00.0093168
//    17. Failed  '        AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
//    18. Failed  '       AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
//    19. Failed  '      AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
//    20. Failed  '     AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
//    21. Failed  '    AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372
Imports System.Diagnostics
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim sw As Stopwatch
        Dim addresses() As String = {"AAAAAAAAAAA@contoso.com",
                                   "AAAAAAAAAAaaaaaaaaaa!@contoso.com"}
        ' The following regular expression should not actually be used to 
        ' validate an email address.
        Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])*$"
        Dim input As String

        For Each address In addresses
            Dim mailBox As String = address.Substring(0, address.IndexOf("@"))
            Dim index As Integer = 0
            For ctr As Integer = mailBox.Length - 1 To 0 Step -1
                index += 1
                input = mailBox.Substring(ctr, index)
                sw = Stopwatch.StartNew()
                Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
                sw.Stop()
                if m.Success Then
                    Console.WriteLine("{0,2}. Matched '{1,25}' in {2}",
                                      index, m.Value, sw.Elapsed)
                Else
                    Console.WriteLine("{0,2}. Failed  '{1,25}' in {2}",
                                      index, input, sw.Elapsed)
                End If
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays output similar to the following:
'     1. Matched '                        A' in 00:00:00.0007122
'     2. Matched '                       AA' in 00:00:00.0000282
'     3. Matched '                      AAA' in 00:00:00.0000042
'     4. Matched '                     AAAA' in 00:00:00.0000038
'     5. Matched '                    AAAAA' in 00:00:00.0000042
'     6. Matched '                   AAAAAA' in 00:00:00.0000042
'     7. Matched '                  AAAAAAA' in 00:00:00.0000042
'     8. Matched '                 AAAAAAAA' in 00:00:00.0000087
'     9. Matched '                AAAAAAAAA' in 00:00:00.0000045
'    10. Matched '               AAAAAAAAAA' in 00:00:00.0000045
'    11. Matched '              AAAAAAAAAAA' in 00:00:00.0000045
'    
'     1. Failed  '                        !' in 00:00:00.0000447
'     2. Failed  '                       a!' in 00:00:00.0000071
'     3. Failed  '                      aa!' in 00:00:00.0000071
'     4. Failed  '                     aaa!' in 00:00:00.0000061
'     5. Failed  '                    aaaa!' in 00:00:00.0000081
'     6. Failed  '                   aaaaa!' in 00:00:00.0000126
'     7. Failed  '                  aaaaaa!' in 00:00:00.0000359
'     8. Failed  '                 aaaaaaa!' in 00:00:00.0000414
'     9. Failed  '                aaaaaaaa!' in 00:00:00.0000758
'    10. Failed  '               aaaaaaaaa!' in 00:00:00.0001462
'    11. Failed  '              aaaaaaaaaa!' in 00:00:00.0002885
'    12. Failed  '             Aaaaaaaaaaa!' in 00:00:00.0005780
'    13. Failed  '            AAaaaaaaaaaa!' in 00:00:00.0011628
'    14. Failed  '           AAAaaaaaaaaaa!' in 00:00:00.0022851
'    15. Failed  '          AAAAaaaaaaaaaa!' in 00:00:00.0045864
'    16. Failed  '         AAAAAaaaaaaaaaa!' in 00:00:00.0093168
'    17. Failed  '        AAAAAAaaaaaaaaaa!' in 00:00:00.0185993
'    18. Failed  '       AAAAAAAaaaaaaaaaa!' in 00:00:00.0366723
'    19. Failed  '      AAAAAAAAaaaaaaaaaa!' in 00:00:00.1370108
'    20. Failed  '     AAAAAAAAAaaaaaaaaaa!' in 00:00:00.1553966
'    21. Failed  '    AAAAAAAAAAaaaaaaaaaa!' in 00:00:00.3223372

Yukarıdaki örnekte gösterilen çıktıda gösterildiği gibi, normal ifade altyapısı geçerli e-posta diğer adını uzunluğuna bakılmaksızın yaklaşık aynı zaman aralığında işler. Öte yandan, neredeyse geçerli e-posta adresinin beşten fazla karakteri olduğunda, işlem süresi dizedeki her fazladan karakter için yaklaşık olarak iki katına çıkarır. Bu nedenle, neredeyse geçerli bir 28 karakterlik dizenin işlenmesi bir saatten fazla sürebilir ve neredeyse geçerli bir 33 karakterlik dizenin işlenmesi yaklaşık bir gün sürebilir.

Bu normal ifade yalnızca eşleştirilecek giriş biçimi dikkate alınarak geliştirildiğinden, desenle eşleşmeyen girişi dikkate alamaz. Bu gözetim de performansı önemli ölçüde düşürmek için normal ifade deseni ile neredeyse eşleşen kısıtlanmamış girişlere izin verebilir.

Bu sorunu çözmek için, şunları yapabilirsiniz:

Nesne örneklemesini uygun şekilde işleme

öğesinin merkezinde. NET'in normal ifade nesne modeli, normal ifade altyapısını System.Text.RegularExpressions.Regex temsil eden sınıfıdır. Genellikle, normal ifade performansını etkileyen tek büyük faktör, altyapının Regex kullanılma şeklidir. Normal bir ifadeyi tanımlama, normal ifade motorunu bir normal ifade deseni ile sıkı şekilde eşlemeyi içerir. Oluşturucusunun normal ifade desenini geçirerek veya statik bir Regex yöntemi çağırarak bir nesneyi normal ifade desenine ve analiz edilecek dizeye geçirerek bir nesnenin örneğini oluşturmayı kapsayan bu eşleştirme işlemi pahalıdır.

Not

Yorumlanmış ve derlenmiş normal ifadeleri kullanmanın performans üzerindeki etkileri hakkında ayrıntılı bir açıklama için Normal İfade Performansını İyi hale getirme, Bölüm II: Backtracking'in Sorumluluğunu Alma blog gönderisine bakın.

Normal ifade altyapısını belirli bir normal ifade deseniyle eşleştirebilir ve ardından altyapıyı kullanarak metni çeşitli yollarla eşleştirebilirsiniz:

  • gibi Regex.Match(String, String)statik desen eşleştirme yöntemini çağırabilirsiniz. Bu yöntem normal ifade nesnesinin örneğini oluşturmayı gerektirmez.

  • Bir Regex nesne örneği oluşturabilir ve normal ifade altyapısını normal ifade desenine bağlamak için varsayılan yöntem olan yorumlanmış normal ifadenin örnek desen eşleştirme yöntemini çağırabilirsiniz. Bir nesnenin örneği bayrağını içeren bir options bağımsız değişken olmadan başlatıldığında sonuçlanır.Compiled Regex

  • Bir nesne örneği Regex oluşturabilir ve kaynak tarafından oluşturulan normal ifadenin örnek desen eşleştirme yöntemini çağırabilirsiniz. Bu teknik çoğu durumda önerilir. Bunu yapmak için özniteliğini GeneratedRegexAttribute döndüren Regexkısmi bir yönteme yerleştirin.

  • Bir Regex nesne örneği oluşturabilir ve derlenmiş normal ifadenin örnek desen eşleştirme yöntemini çağırabilirsiniz. Normal ifade nesneleri, bir nesne bayrağını içeren bir Regex options bağımsız değişkenle örneklendiğinde derlenmiş desenleri temsil eder Compiled .

Normal ifade eşleştirme yöntemlerini çağırmanın belirli bir yolu uygulamanızın performansını etkileyebilir. Aşağıdaki bölümlerde uygulamanızın performansını artırmak için statik yöntem çağrılarının, kaynak tarafından oluşturulan normal ifadelerin, yorumlanan normal ifadelerin ve derlenmiş normal ifadelerin ne zaman kullanılacağı açıklanmıştır.

Önemli

Yöntem çağrısının biçimi (statik, yorumlanmış, kaynak tarafından oluşturulan, derlenmiş), yöntem çağrılarında aynı normal ifade tekrar tekrar kullanılıyorsa veya bir uygulama normal ifade nesnelerini kapsamlı bir şekilde kullanıyorsa performansı etkiler.

Statik normal ifadeler

Statik normal ifade yöntemleri, bir normal ifade nesnesine aynı normal ifadeyi tekrar tekrar ön değer olarak atamaya alternatif olarak önerilir. Normal ifade nesneleri tarafından kullanılan normal ifade desenlerinden farklı olarak, statik yöntem çağrılarında kullanılan desenlerden işlem kodları (opcodes) veya derlenmiş ortak ara dil (CIL) normal ifade altyapısı tarafından dahili olarak önbelleğe alınır.

Örneğin bir olay işleyicisi, kullanıcı girişini onaylamak için sık sık başka bir yöntem çağırır. Bu örnek, Button bir denetimin Click olayının adlı IsValidCurrencybir yöntemi çağırmak için kullanıldığı ve kullanıcının en az bir ondalık basamak ile bir para birimi simgesi girip girmediğini denetleyen aşağıdaki koda yansıtılır.

public void OKButton_Click(object sender, EventArgs e)
{
   if (! String.IsNullOrEmpty(sourceCurrency.Text))
      if (RegexLib.IsValidCurrency(sourceCurrency.Text))
         PerformConversion();
      else
         status.Text = "The source currency value is invalid.";
}
Public Sub OKButton_Click(sender As Object, e As EventArgs) _
           Handles OKButton.Click

    If Not String.IsNullOrEmpty(sourceCurrency.Text) Then
        If RegexLib.IsValidCurrency(sourceCurrency.Text) Then
            PerformConversion()
        Else
            status.Text = "The source currency value is invalid."
        End If
    End If
End Sub

Aşağıdaki örnekte yönteminin IsValidCurrency verimsiz bir uygulaması gösterilmiştir:

Not

Her yöntem çağrısı aynı desene sahip bir Regex nesneyi yeniden doğrular. Bu ise normal ifade deseninin yöntem her çağrıldığında tekrar derlenmesi gerektiği anlamına gelir.

using System;
using System.Text.RegularExpressions;

public class RegexLib
{
   public static bool IsValidCurrency(string currencyValue)
   {
      string pattern = @"\p{Sc}+\s*\d+";
      Regex currencyRegex = new Regex(pattern);
      return currencyRegex.IsMatch(currencyValue);
   }
}
Imports System.Text.RegularExpressions

Public Module RegexLib
    Public Function IsValidCurrency(currencyValue As String) As Boolean
        Dim pattern As String = "\p{Sc}+\s*\d+"
        Dim currencyRegex As New Regex(pattern)
        Return currencyRegex.IsMatch(currencyValue)
    End Function
End Module

Önceki verimsiz kodu statik Regex.IsMatch(String, String) yönteme yapılan bir çağrıyla değiştirmeniz gerekir. Bu yaklaşım, desen eşleştirme yöntemini her çağırmak istediğinizde bir Regex nesnenin örneğini oluşturma gereksinimini ortadan kaldırır ve normal ifade altyapısının normal ifadenin derlenmiş bir sürümünü önbelleğinden almasını sağlar.

using System;
using System.Text.RegularExpressions;

public class RegexLib2
{
   public static bool IsValidCurrency(string currencyValue)
   {
      string pattern = @"\p{Sc}+\s*\d+";
      return Regex.IsMatch(currencyValue, pattern);
   }
}
Imports System.Text.RegularExpressions

Public Module RegexLib
    Public Function IsValidCurrency(currencyValue As String) As Boolean
        Dim pattern As String = "\p{Sc}+\s*\d+"
        Return Regex.IsMatch(currencyValue, pattern)
    End Function
End Module

Varsayılan olarak, en son kullanılan 15 statik normal ifade deseni önbelleğe alınır. Daha fazla sayıda önbelleğe alınmış statik normal ifade gerektiren uygulamalar için, özelliği ayarlanarak Regex.CacheSize önbelleğin boyutu ayarlanabilir.

Bu örnekte kullanılan normal ifade \p{Sc}+\s*\d+ , giriş dizesinin para birimi simgesi ve en az bir ondalık basamak olduğunu doğrular. Desen aşağıdaki tabloda gösterildiği gibi tanımlanır:

Desen Açıklama
\p{Sc}+ Unicode Simgesi, Para Birimi kategorisindeki bir veya daha fazla karakterle eşleşir.
\s* Sıfır veya daha fazla boşluk karakteriyle eşleşir.
\d+ Bir veya daha fazla ondalık basamakla eşleşir.

Yorumlanmış ve kaynak tarafından oluşturulan ve derlenmiş normal ifadeler karşılaştırması

Seçeneğin belirtimi Compiled aracılığıyla normal ifade altyapısına bağlı olmayan normal ifade desenleri yorumlanır. Bir normal ifade nesnesi örneği oluşturulduğunda, normal ifade altyapısı normal ifadeyi bir dizi işlem koduna dönüştürür. Bir örnek yöntemi çağrıldığında, işlem kodları CIL'ye dönüştürülür ve JIT derleyicisi tarafından yürütülür. Benzer şekilde, statik bir normal ifade yöntemi çağrıldığında ve normal ifade önbellekte bulunamadığında, normal ifade altyapısı normal ifadeyi bir dizi işlem koduna dönüştürür ve bunları önbellekte depolar. Ardından JIT derleyicisinin yürütebilmesi için bu işlem kodlarını CIL'ye dönüştürür. Yorumlanmış normal ifadeler, daha yavaş yürütme sürelerine karşın açılış süresini azaltır. Bu işlem nedeniyle, normal ifade az sayıda yöntem çağrısında kullanıldığında veya normal ifade yöntemlerine yapılan çağrıların tam sayısı bilinmiyorsa ancak küçük olması bekleniyorsa en iyi şekilde kullanılırlar. Yöntem çağrıları arttıkça performans kazancı daha az başlangıç saatinden sayısı daha yavaş yürütme hızını outstripped gibi.

Seçeneğin belirtimi Compiled aracılığıyla normal ifade altyapısına bağlı normal ifade desenleri derlenir. Bu nedenle, bir normal ifade nesnesi örneği oluşturulduğunda veya statik bir normal ifade yöntemi çağrıldığında ve normal ifade önbellekte bulunamadığında, normal ifade altyapısı normal ifadeyi bir aracı işlem kodları kümesine dönüştürür. Bu kodlar daha sonra CIL'ye dönüştürülür. Bir yöntem çağrıldığında, JIT derleyicisi CIL'yi yürütür. Yorumlanmış normal ifadelerin aksine, derlenmiş normal ifadeler açılış süresini artırır ancak ayrı desen eşleme yöntemlerini daha hızlı yürütür. Sonuç olarak normal ifade derlemekten kaynaklanan sonuçlardan yararlanan performans çağrılan normal ifade yöntemlerinin sayısı oranında artar.

özniteliğine sahip bir -returning yönteminin donatma Regexyoluyla normal ifade altyapısına GeneratedRegexAttribute bağlı normal ifade desenleri kaynak oluşturulur. Derleyiciye bağlanan kaynak oluşturucu, C# kodu olarak özel türetilmiş bir uygulama ve CIL'de yayılana RegexOptions.Compiled benzer bir Regexmantık gösterir. 'nin RegexOptions.Compiled tüm aktarım hızı performans avantajlarını (daha doğrusu) ve başlangıç avantajlarını Regex.CompileToAssembly, ancak karmaşıklığı CompileToAssemblyolmadan elde edersiniz. Yayılan kaynak projenizin bir parçasıdır ve bu da kolayca görüntülenebilir ve hata ayıklanabilir olduğu anlamına gelir.

Özetlemek gerekirse şunları yapmanızı öneririz:

  • Normal ifade yöntemlerini belirli bir normal ifadeyle nispeten seyrek çağırdığınızda yorumlanan normal ifadeleri kullanın.
  • C# dilinde derleme zamanında bilinen bağımsız değişkenlerle kullanıyorsanız Regex ve belirli bir normal ifadeyi nispeten sık kullanıyorsanız kaynak tarafından oluşturulan normal ifadeleri kullanın.
  • Belirli bir normal ifadeyle normal ifade yöntemlerini nispeten sık çağırırken ve .NET 6 veya önceki bir sürümü kullanırken derlenmiş normal ifadeleri kullanın.

Yorumlanan normal ifadelerin daha yavaş yürütme hızlarının azaltılmış başlangıç sürelerinden daha ağır bastığı eşiği belirlemek zordur. Ayrıca, kaynak tarafından oluşturulan veya derlenen normal ifadelerin daha yavaş başlatma sürelerinin daha yüksek yürütme hızlarından daha ağır basdığı eşiği belirlemek de zordur. Eşikler, normal ifadenin karmaşıklığı ve işlediği belirli veriler de dahil olmak üzere çeşitli faktörlere bağlıdır. Belirli bir uygulama senaryonuz için en iyi performansı sunan normal ifadeleri belirlemek için sınıfını Stopwatch kullanarak yürütme sürelerini karşılaştırabilirsiniz.

Aşağıdaki örnek, ilk 10 cümleyi okurken ve William D. Guthrie'nin Magna Carta ve Diğer Adresler metinlerindeki tüm cümleleri okurken derlenmiş, kaynak tarafından oluşturulan ve yorumlanan normal ifadelerin performansını karşılaştırır. Örnekteki çıktıda gösterildiği gibi, normal ifade eşleştirme yöntemlerine yalnızca 10 çağrı yapıldığında, yorumlanmış veya kaynak tarafından oluşturulan normal ifade, derlenmiş normal ifadeden daha iyi performans sunar. Ancak derlenmiş bir normal ifade, daha fazla sayıda çağrı yapıldığında (bu örnekte 13.000'den fazla) daha iyi performans gösterir.

const string Pattern = @"\b(\w+((\r?\n)|,?\s))*\w+[.?:;!]";

static readonly HttpClient s_client = new();

[GeneratedRegex(Pattern, RegexOptions.Singleline)]
private static partial Regex GeneratedRegex();

public async static Task RunIt()
{
    Stopwatch sw;
    Match match;
    int ctr;

    string text =
            await s_client.GetStringAsync("https://www.gutenberg.org/cache/epub/64197/pg64197.txt");

    // Read first ten sentences with interpreted regex.
    Console.WriteLine("10 Sentences with Interpreted Regex:");
    sw = Stopwatch.StartNew();
    Regex int10 = new(Pattern, RegexOptions.Singleline);
    match = int10.Match(text);
    for (ctr = 0; ctr <= 9; ctr++)
    {
        if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
        else
            break;
    }
    sw.Stop();
    Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed);

    // Read first ten sentences with compiled regex.
    Console.WriteLine("10 Sentences with Compiled Regex:");
    sw = Stopwatch.StartNew();
    Regex comp10 = new Regex(Pattern,
                 RegexOptions.Singleline | RegexOptions.Compiled);
    match = comp10.Match(text);
    for (ctr = 0; ctr <= 9; ctr++)
    {
        if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
        else
            break;
    }
    sw.Stop();
    Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed);

    // Read first ten sentences with source-generated regex.
    Console.WriteLine("10 Sentences with Source-generated Regex:");
    sw = Stopwatch.StartNew();

    match = GeneratedRegex().Match(text);
    for (ctr = 0; ctr <= 9; ctr++)
    {
        if (match.Success)
            // Do nothing with the match except get the next match.
            match = match.NextMatch();
        else
            break;
    }
    sw.Stop();
    Console.WriteLine("   {0} matches in {1}", ctr, sw.Elapsed);

    // Read all sentences with interpreted regex.
    Console.WriteLine("All Sentences with Interpreted Regex:");
    sw = Stopwatch.StartNew();
    Regex intAll = new(Pattern, RegexOptions.Singleline);
    match = intAll.Match(text);
    int matches = 0;
    while (match.Success)
    {
        matches++;
        // Do nothing with the match except get the next match.
        match = match.NextMatch();
    }
    sw.Stop();
    Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);

    // Read all sentences with compiled regex.
    Console.WriteLine("All Sentences with Compiled Regex:");
    sw = Stopwatch.StartNew();
    Regex compAll = new(Pattern,
                    RegexOptions.Singleline | RegexOptions.Compiled);
    match = compAll.Match(text);
    matches = 0;
    while (match.Success)
    {
        matches++;
        // Do nothing with the match except get the next match.
        match = match.NextMatch();
    }
    sw.Stop();
    Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);

    // Read all sentences with source-generated regex.
    Console.WriteLine("All Sentences with Source-generated Regex:");
    sw = Stopwatch.StartNew();
    match = GeneratedRegex().Match(text);
    matches = 0;
    while (match.Success)
    {
        matches++;
        // Do nothing with the match except get the next match.
        match = match.NextMatch();
    }
    sw.Stop();
    Console.WriteLine("   {0:N0} matches in {1}", matches, sw.Elapsed);

    return;
}
/* The example displays output similar to the following:

   10 Sentences with Interpreted Regex:
       10 matches in 00:00:00.0104920
   10 Sentences with Compiled Regex:
       10 matches in 00:00:00.0234604
   10 Sentences with Source-generated Regex:
       10 matches in 00:00:00.0060982
   All Sentences with Interpreted Regex:
       3,427 matches in 00:00:00.1745455
   All Sentences with Compiled Regex:
       3,427 matches in 00:00:00.0575488
   All Sentences with Source-generated Regex:
       3,427 matches in 00:00:00.2698670
*/

örneğinde \b(\w+((\r?\n)|,?\s))*\w+[.?:;!]kullanılan normal ifade deseni aşağıdaki tabloda gösterildiği gibi tanımlanır:

Desen Açıklama
\b Bir sözcük sınırında eşleşmeye başla.
\w+ Bir veya daha fazla sözcük karakteriyle eşleşir.
(\r?\n)|,?\s) Sıfır veya bir satır başı ve ardından yeni satır karakteri ya da sıfır veya bir virgül ve ardından boşluk karakteri ile eşleşir.
(\w+((\r?\n)|,?\s))* Bir veya daha fazla sözcük karakterinin sıfır veya bir satır başı ve yeni satır karakteri ya da sıfır ya da bir virgül ve ardından boşluk karakteri gelen sıfır veya daha fazla oluşumuyla eşleşir.
\w+ Bir veya daha fazla sözcük karakteriyle eşleşir.
[.?:;!] Nokta, soru işareti, iki nokta üst üste, noktalı virgül veya ünlem işaretiyle eşleşir.

Geri izlemenin sorumluluğunu üstlenme

Sıradan şekilde, normal ifade motoru bir giriş dizsi içinde ilerlemek ve bunu bir normal ifade deseni ile karşılaştırmak için doğrusal ilerlemeyi kullanır. Ancak, , ve ? gibi *+belirsiz niceleyiciler normal ifade deseninde kullanıldığında, normal ifade altyapısı başarılı kısmi eşleşmelerin bir bölümünü verebilir ve desenin tamamı için başarılı bir eşleşme aramak için daha önce kaydedilmiş bir duruma geri dönebilir. Bu işlem geri dönüş olarak bilinir.

İpucu

Geri izleme hakkında daha fazla bilgi için bkz . Normal ifade davranışının ayrıntıları ve Geri İzleme. Geri izleme hakkında ayrıntılı tartışmalar için .NET 7'de Normal İfade geliştirmeleri ve Normal İfade Performansını İyileştirme blog gönderilerine bakın.

Geri dönüş için destek, normal ifadelere güç ve esneklik kazandırır. Ayrıca normal ifade motorunun çalışmasının denetlenmesini sorumluluğunu normal ifade geliştiricisine teslim eder. Geliştiriciler genelde bu sorumluluğun farkında olmadığından, geri dönüşü yanlış kullanmaları ya da aşırı geri dönüşe bağımlılıkları genelde normal ifade performansının düşmesinde önemli bir rol oynar. En kötü senaryoda yürütme süresi girdi dizesinde her ek karakter ile iki katına çıkar. Aslında, geri izlemeyi aşırı derecede kullanarak, giriş normal ifade deseni ile neredeyse eşleşiyorsa sonsuz döngünün programlı eşdeğerini oluşturmak kolaydır. Normal ifade altyapısının nispeten kısa bir giriş dizesini işlemesi saatler, hatta günler sürebilir.

Genellikle, bir eşleşme için geri izleme gerekli olmasa da uygulamalar geri izleme kullanmak için bir performans cezası öder. Örneğin, normal ifade \b\p{Lu}\w*\b , aşağıdaki tabloda gösterildiği gibi büyük harfle başlayan tüm sözcüklerle eşleşir:

Desen Açıklama
\b Bir sözcük sınırında eşleşmeye başla.
\p{Lu} Büyük harf karakterle eşleşir.
\w* Sıfır veya daha fazla sözcük karakteriyle eşleşir.
\b Eşlemeyi bir sözcük sınırında sonlandır.

Sözcük sınırı, bir sözcük karakteriyle aynı veya bir alt kümesi olmadığından, normal ifade altyapısının sözcük karakterleri eşleştirirken sözcük sınırını aşma olasılığı yoktur. Bu nedenle bu normal ifade için geri izleme hiçbir eşleşmenin genel başarısına katkıda bulunamayacaktır. Yalnızca normal ifade altyapısı bir sözcük karakterinin her başarılı ön eşleşmesi için durumunu kaydetmeye zorlandığından performansı düşürebilir.

Geri izlemenin gerekli olmadığını belirlerseniz, bunu birkaç yolla devre dışı bırakabilirsiniz:

  • seçeneğini ayarlayarak RegexOptions.NonBacktracking (.NET 7'de kullanıma sunulmuştur). Daha fazla bilgi için bkz . Geri izleme modu.

  • Atomik grup olarak bilinen dil öğesini kullanarak (?>subexpression) . Aşağıdaki örnek, bir girdi dizesini iki normal ifade kullanarak ayrıştırmaktadır. İlki, \b\p{Lu}\w*\bgeri izlemeye dayanır. İkincisi, \b\p{Lu}(?>\w*)\bgeri izlemeyi devre dışı bırakır. Örnekteki çıktıda gösterildiği gibi, ikisi de aynı sonucu üretir:

    using System;
    using System.Text.RegularExpressions;
    
    public class BackTrack2Example
    {
        public static void Main()
        {
            string input = "This this word Sentence name Capital";
            string pattern = @"\b\p{Lu}\w*\b";
            foreach (Match match in Regex.Matches(input, pattern))
                Console.WriteLine(match.Value);
    
            Console.WriteLine();
    
            pattern = @"\b\p{Lu}(?>\w*)\b";
            foreach (Match match in Regex.Matches(input, pattern))
                Console.WriteLine(match.Value);
        }
    }
    // The example displays the following output:
    //       This
    //       Sentence
    //       Capital
    //
    //       This
    //       Sentence
    //       Capital
    
    Imports System.Text.RegularExpressions
    
    Module Example
        Public Sub Main()
            Dim input As String = "This this word Sentence name Capital"
            Dim pattern As String = "\b\p{Lu}\w*\b"
            For Each match As Match In Regex.Matches(input, pattern)
                Console.WriteLine(match.Value)
            Next
            Console.WriteLine()
    
            pattern = "\b\p{Lu}(?>\w*)\b"
            For Each match As Match In Regex.Matches(input, pattern)
                Console.WriteLine(match.Value)
            Next
        End Sub
    End Module
    ' The example displays the following output:
    '       This
    '       Sentence
    '       Capital
    '       
    '       This
    '       Sentence
    '       Capital
    

Birçok durumda, geri izleme bir normal ifade desenini giriş metnine eşlemek için gereklidir. Ancak aşırı geri izleme performansı ciddi şekilde azaltabilir ve uygulamanın yanıt vermediği izlenimine yol açabilir. Özellikle bu sorun, niceleyiciler iç içe yerleştirildiğinde ve dış alt ifadeyle eşleşen metin, iç alt ifadeyle eşleşen metnin bir alt kümesi olduğunda ortaya çıkar.

Uyarı

Aşırı geri izlemeden kaçınmanın yanı sıra, aşırı geri izlemenin normal ifade performansını ciddi ölçüde düşürmediğinden emin olmak için zaman aşımı özelliğini kullanmanız gerekir. Daha fazla bilgi için Zaman aşımı değerlerini kullanma bölümüne bakın.

Örneğin, normal ifade deseni ^[0-9A-Z]([-.\w]*[0-9A-Z])*\$$ en az bir alfasayısal karakterden oluşan bir parça numarasıyla eşleşmeye yöneliktir. Bir ek karakter bir alfasayısal karakter, bir ayırma çizgisi, bir alt çizgi ya da bir nokta olabilir, ancak son karakter alfasayısal olmalıdır. Bir dolar işareti parça numarasını sonlandırır. Bazı durumlarda, nicelleyiciler iç içe yerleştirildiğinden ve alt ifade [0-9A-Z] alt ifadenin bir alt kümesi olduğundan bu normal ifade deseni [-.\w]*düşük performans sergileyebilir.

Bu durumlarda, yuvalanan miktar niceleyicileri kaldırarak ve dış alt ifadeyi sıfır genişliğinde bir ileriye dönük ya da geriye dönük onay ile değiştirerek normal ifade performansını en iyi hale getireceksiniz. Lookahead ve lookbehind onayları yer işaretidir. Bunlar, işaretçiyi giriş dizesinde taşımaz, ancak belirtilen koşulun karşılanıp karşılanmadığını denetlemek için ileriye veya arkaya bakar. Örneğin, normal parça numarası ifadesi olarak ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$yeniden yazılabilir. Bu normal ifade düzeni aşağıdaki tabloda gösterildiği gibi tanımlanır:

Desen Açıklama
^ Giriş dizesinin başında eşleşmeye başla.
[0-9A-Z] Alfasayısal bir karakterle eşleştirin Parça numarası en azından bu karakteri içermelidir.
[-.\w]* Herhangi bir sözcük karakteri, kesme ya da noktanın sıfır ya da daha fazla oluşumunu eşleyin.
\$ Bir dolar işareti eşleyin.
(?<=[0-9A-Z]) Önceki karakterin alfasayısal olduğundan emin olmak için bitiş doları işaretinin arkasına bakın.
$ Giriş dizesinin sonunda eşleşmeyi bitir.

Aşağıdaki örnekte, bu normal ifadenin olası parça numaraları içeren bir diziyle eşleşecek şekilde kullanılması gösterilmektedir:

using System;
using System.Text.RegularExpressions;

public class BackTrack4Example
{
    public static void Main()
    {
        string pattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$";
        string[] partNos = { "A1C$", "A4", "A4$", "A1603D$", "A1603D#" };

        foreach (var input in partNos)
        {
            Match match = Regex.Match(input, pattern);
            if (match.Success)
                Console.WriteLine(match.Value);
            else
                Console.WriteLine("Match not found.");
        }
    }
}
// The example displays the following output:
//       A1C$
//       Match not found.
//       A4$
//       A1603D$
//       Match not found.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim pattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])\$$"
        Dim partNos() As String = {"A1C$", "A4", "A4$", "A1603D$",
                                    "A1603D#"}

        For Each input As String In partNos
            Dim match As Match = Regex.Match(input, pattern)
            If match.Success Then
                Console.WriteLine(match.Value)
            Else
                Console.WriteLine("Match not found.")
            End If
        Next
    End Sub
End Module
' The example displays the following output:
'       A1C$
'       Match not found.
'       A4$
'       A1603D$
'       Match not found.

.NET'teki normal ifade dili, iç içe niceleyicileri ortadan kaldırmak için kullanabileceğiniz aşağıdaki dil öğelerini içerir. Daha fazla bilgi için bkz . Yapıları gruplandırma.

Dil öğesi Açıklama
(?= subexpression ) Sıfır genişlikli pozitif ileriye yönelik onay. Giriş dizesiyle eşleşip eşleşmediğini subexpression belirlemek için geçerli konumun önüne bakar.
(?! subexpression ) Sıfır genişlikli negatif ileriye yönelik onay. Giriş dizesiyle eşleşmediğini subexpression belirlemek için geçerli konumun önüne bakar.
(?<= subexpression ) Sıfır genişlikli pozitif geriye yönelik onay. Giriş dizesiyle eşleşip eşleşmediğini subexpression belirlemek için geçerli konumun arkasına bakar.
(?<! subexpression ) Sıfır genişlikli negatif geriye yönelik onay. Giriş dizesiyle eşleşmediğini subexpression belirlemek için geçerli konumun arkasına bakar.

Zaman aşımı değerlerini kullanma

Normal ifadeleriniz, normal ifade deseniyle neredeyse eşleşen girişleri işleme alıyorsa, sıkça aşırı geri izlemeye dayanıyor olabilir, bu da performansı önemli ölçüde etkiler. Geri izleme kullanımınızı ve normal ifadeyi yakın eşleşen girişe karşı test etmeyi dikkatle düşünmenin yanı sıra, aşırı geri izlemenin etkisini en aza indirmek için her zaman bir zaman aşımı değeri ayarlamanız gerekir.

Normal ifade zaman aşımı aralığı, normal ifade altyapısının zaman aşımına uğramadan önce tek bir eşleşme arayacağı süreyi tanımlar. Normal ifade düzenine ve giriş metnine bağlı olarak, yürütme süresi belirtilen zaman aşımı aralığını aşabilir, ancak belirtilen zaman aşımı aralığından daha fazla geri izleme harcamaz. Varsayılan zaman aşımı aralığı olur Regex.InfiniteMatchTimeout. Bu, normal ifadenin zaman aşımına olmayacağı anlamına gelir. Bu değeri geçersiz kılabilir ve aşağıdaki gibi bir zaman aşımı aralığı tanımlayabilirsiniz:

Zaman aşımı aralığı tanımladıysanız ve bu aralığın sonunda eşleşme bulunmazsa, normal ifade yöntemi bir RegexMatchTimeoutException özel durum oluşturur. Özel durum işleyicinizde, eşleşmeyi daha uzun bir zaman aşımı aralığıyla yeniden denemeyi, eşleşme denemesini bırakmayı ve eşleşme olmadığını varsaymayı veya eşleştirme denemesini bırakıp gelecekteki analiz için özel durum bilgilerini günlüğe kaydetmeyi seçebilirsiniz.

Aşağıdaki örnek, bir metin belgesindeki bir GetWordData sözcüğün sözcük sayısını ve ortalama karakter sayısını hesaplamak için 350 milisaniyelik zaman aşımı aralığıyla normal ifade örneği oluşturan bir yöntemi tanımlar. Eşleşen işlem zaman aşımına uğrıyorsa zaman aşımı aralığı 350 milisaniye artırılır ve Regex nesne yeniden oluşturulur. Yeni zaman aşımı aralığı bir saniyeyi aşarsa, yöntemi çağıranın özel durumunu yeniden oluşturur.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;

public class TimeoutExample
{
    public static void Main()
    {
        RegexUtilities util = new RegexUtilities();
        string title = "Doyle - The Hound of the Baskervilles.txt";
        try
        {
            var info = util.GetWordData(title);
            Console.WriteLine("Words:               {0:N0}", info.Item1);
            Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2);
        }
        catch (IOException e)
        {
            Console.WriteLine("IOException reading file '{0}'", title);
            Console.WriteLine(e.Message);
        }
        catch (RegexMatchTimeoutException e)
        {
            Console.WriteLine("The operation timed out after {0:N0} milliseconds",
                              e.MatchTimeout.TotalMilliseconds);
        }
    }
}

public class RegexUtilities
{
    public Tuple<int, double> GetWordData(string filename)
    {
        const int MAX_TIMEOUT = 1000;   // Maximum timeout interval in milliseconds.
        const int INCREMENT = 350;      // Milliseconds increment of timeout.

        List<string> exclusions = new List<string>(new string[] { "a", "an", "the" });
        int[] wordLengths = new int[29];        // Allocate an array of more than ample size.
        string input = null;
        StreamReader sr = null;
        try
        {
            sr = new StreamReader(filename);
            input = sr.ReadToEnd();
        }
        catch (FileNotFoundException e)
        {
            string msg = String.Format("Unable to find the file '{0}'", filename);
            throw new IOException(msg, e);
        }
        catch (IOException e)
        {
            throw new IOException(e.Message, e);
        }
        finally
        {
            if (sr != null) sr.Close();
        }

        int timeoutInterval = INCREMENT;
        bool init = false;
        Regex rgx = null;
        Match m = null;
        int indexPos = 0;
        do
        {
            try
            {
                if (!init)
                {
                    rgx = new Regex(@"\b\w+\b", RegexOptions.None,
                                    TimeSpan.FromMilliseconds(timeoutInterval));
                    m = rgx.Match(input, indexPos);
                    init = true;
                }
                else
                {
                    m = m.NextMatch();
                }
                if (m.Success)
                {
                    if (!exclusions.Contains(m.Value.ToLower()))
                        wordLengths[m.Value.Length]++;

                    indexPos += m.Length + 1;
                }
            }
            catch (RegexMatchTimeoutException e)
            {
                if (e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT)
                {
                    timeoutInterval += INCREMENT;
                    init = false;
                }
                else
                {
                    // Rethrow the exception.
                    throw;
                }
            }
        } while (m.Success);

        // If regex completed successfully, calculate number of words and average length.
        int nWords = 0;
        long totalLength = 0;

        for (int ctr = wordLengths.GetLowerBound(0); ctr <= wordLengths.GetUpperBound(0); ctr++)
        {
            nWords += wordLengths[ctr];
            totalLength += ctr * wordLengths[ctr];
        }
        return new Tuple<int, double>(nWords, totalLength / nWords);
    }
}
Imports System.Collections.Generic
Imports System.IO
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim util As New RegexUtilities()
        Dim title As String = "Doyle - The Hound of the Baskervilles.txt"
        Try
            Dim info = util.GetWordData(title)
            Console.WriteLine("Words:               {0:N0}", info.Item1)
            Console.WriteLine("Average Word Length: {0:N2} characters", info.Item2)
        Catch e As IOException
            Console.WriteLine("IOException reading file '{0}'", title)
            Console.WriteLine(e.Message)
        Catch e As RegexMatchTimeoutException
            Console.WriteLine("The operation timed out after {0:N0} milliseconds",
                              e.MatchTimeout.TotalMilliseconds)
        End Try
    End Sub
End Module

Public Class RegexUtilities
    Public Function GetWordData(filename As String) As Tuple(Of Integer, Double)
        Const MAX_TIMEOUT As Integer = 1000  ' Maximum timeout interval in milliseconds.
        Const INCREMENT As Integer = 350     ' Milliseconds increment of timeout.

        Dim exclusions As New List(Of String)({"a", "an", "the"})
        Dim wordLengths(30) As Integer        ' Allocate an array of more than ample size.
        Dim input As String = Nothing
        Dim sr As StreamReader = Nothing
        Try
            sr = New StreamReader(filename)
            input = sr.ReadToEnd()
        Catch e As FileNotFoundException
            Dim msg As String = String.Format("Unable to find the file '{0}'", filename)
            Throw New IOException(msg, e)
        Catch e As IOException
            Throw New IOException(e.Message, e)
        Finally
            If sr IsNot Nothing Then sr.Close()
        End Try

        Dim timeoutInterval As Integer = INCREMENT
        Dim init As Boolean = False
        Dim rgx As Regex = Nothing
        Dim m As Match = Nothing
        Dim indexPos As Integer = 0
        Do
            Try
                If Not init Then
                    rgx = New Regex("\b\w+\b", RegexOptions.None,
                                    TimeSpan.FromMilliseconds(timeoutInterval))
                    m = rgx.Match(input, indexPos)
                    init = True
                Else
                    m = m.NextMatch()
                End If
                If m.Success Then
                    If Not exclusions.Contains(m.Value.ToLower()) Then
                        wordLengths(m.Value.Length) += 1
                    End If
                    indexPos += m.Length + 1
                End If
            Catch e As RegexMatchTimeoutException
                If e.MatchTimeout.TotalMilliseconds < MAX_TIMEOUT Then
                    timeoutInterval += INCREMENT
                    init = False
                Else
                    ' Rethrow the exception.
                    Throw
                End If
            End Try
        Loop While m.Success

        ' If regex completed successfully, calculate number of words and average length.
        Dim nWords As Integer
        Dim totalLength As Long

        For ctr As Integer = wordLengths.GetLowerBound(0) To wordLengths.GetUpperBound(0)
            nWords += wordLengths(ctr)
            totalLength += ctr * wordLengths(ctr)
        Next
        Return New Tuple(Of Integer, Double)(nWords, totalLength / nWords)
    End Function
End Class

Yalnızca gerektiğinde yakala

.NET'teki normal ifadeler, normal ifade desenini bir veya daha fazla alt ifadede gruplandırmanıza olanak tanıyan gruplandırma yapılarını destekler. .NET normal ifade dilinde (en yaygın kullanılan gruplandırma yapıları, numaralandırılmış bir yakalama grubunu tanımlayan alt) ifade ve(?< adlandırılmış bir yakalama grubunu tanımlayan ad>alt) ifadesidir. Yapı birimlerini gruplamak geri başvuruları oluşturmak ve bir miktar niceleyicinin uygulandığı bir alt ifade tanımlamak için gereklidir.

Ancak bu dil öğelerinin kullanılmasının bir maliyeti vardır. Özelliği tarafından döndürülen nesnenin GroupCollection Match.Groups en son adlandırılmamış veya adlandırılmış yakalamalarla doldurulmasına neden olurlar. Tek bir gruplandırma yapısı giriş dizesinde birden çok alt dize yakalamışsa, belirli bir yakalama grubunun özelliği tarafından Group.Captures döndürülen nesneyi de birden çok Capture nesneyle doldururCaptureCollection.

Gruplandırma yapıları genellikle yalnızca normal ifadede kullanılır, böylece niceleyiciler bunlara uygulanabilir. Bu alt ifadeler tarafından yakalanan gruplar daha sonra kullanılmaz. Örneğin, normal ifade \b(\w+[;,]?\s?)+[.?!] tümceyi yakalamak için tasarlanmıştır. Aşağıdaki tabloda, bu normal ifade desenindeki dil öğeleri ve bunların nesnenin Match Match.Groups ve Group.Captures koleksiyonları üzerindeki etkisi açıklanmaktadır:

Desen Açıklama
\b Bir sözcük sınırında eşleşmeye başla.
\w+ Bir veya daha fazla sözcük karakteriyle eşleşir.
[;,]? Sıfır veya bir virgül veya noktalı virgülle eşleşir.
\s? Sıfır veya bir boşluk karakteriyle eşleşir.
(\w+[;,]?\s?)+ Bir veya daha fazla sözcük karakterinin bir veya daha fazla tekrarını, ardından isteğe bağlı virgül veya noktalı virgül ve ardından isteğe bağlı boşluk karakteriyle eşleşir. Bu desen, birden çok sözcük karakterinin (başka bir deyişle, bir sözcük) ve ardından isteğe bağlı bir noktalama simgesinin birleşiminin normal ifade altyapısı cümlenin sonuna ulaşana kadar yinelenmesi için gerekli olan ilk yakalama grubunu tanımlar.
[.?!] Nokta, soru işareti veya ünlem işaretiyle eşleşir.

Aşağıdaki örnekte gösterildiği gibi, bir eşleşme bulunduğunda GroupCollection hem hem de CaptureCollection nesneleri eşleşmeden alınan yakalamalarla doldurulur. Bu durumda, niceleyicinin + uygulanabilmesi için yakalama grubu (\w+[;,]?\s?) vardır ve bu da normal ifade deseninin tümcedeki her sözcükle eşleşmesini sağlar. Aksi halde bir cümledeki son sözcüğü eşleyebilir.

using System;
using System.Text.RegularExpressions;

public class Group1Example
{
    public static void Main()
    {
        string input = "This is one sentence. This is another.";
        string pattern = @"\b(\w+[;,]?\s?)+[.?!]";

        foreach (Match match in Regex.Matches(input, pattern))
        {
            Console.WriteLine("Match: '{0}' at index {1}.",
                              match.Value, match.Index);
            int grpCtr = 0;
            foreach (Group grp in match.Groups)
            {
                Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                                  grpCtr, grp.Value, grp.Index);
                int capCtr = 0;
                foreach (Capture cap in grp.Captures)
                {
                    Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                      capCtr, cap.Value, cap.Index);
                    capCtr++;
                }
                grpCtr++;
            }
            Console.WriteLine();
        }
    }
}
// The example displays the following output:
//       Match: 'This is one sentence.' at index 0.
//          Group 0: 'This is one sentence.' at index 0.
//             Capture 0: 'This is one sentence.' at 0.
//          Group 1: 'sentence' at index 12.
//             Capture 0: 'This ' at 0.
//             Capture 1: 'is ' at 5.
//             Capture 2: 'one ' at 8.
//             Capture 3: 'sentence' at 12.
//
//       Match: 'This is another.' at index 22.
//          Group 0: 'This is another.' at index 22.
//             Capture 0: 'This is another.' at 22.
//          Group 1: 'another' at index 30.
//             Capture 0: 'This ' at 22.
//             Capture 1: 'is ' at 27.
//             Capture 2: 'another' at 30.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim input As String = "This is one sentence. This is another."
        Dim pattern As String = "\b(\w+[;,]?\s?)+[.?!]"

        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Match: '{0}' at index {1}.",
                              match.Value, match.Index)
            Dim grpCtr As Integer = 0
            For Each grp As Group In match.Groups
                Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                                  grpCtr, grp.Value, grp.Index)
                Dim capCtr As Integer = 0
                For Each cap As Capture In grp.Captures
                    Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                      capCtr, cap.Value, cap.Index)
                    capCtr += 1
                Next
                grpCtr += 1
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output:
'       Match: 'This is one sentence.' at index 0.
'          Group 0: 'This is one sentence.' at index 0.
'             Capture 0: 'This is one sentence.' at 0.
'          Group 1: 'sentence' at index 12.
'             Capture 0: 'This ' at 0.
'             Capture 1: 'is ' at 5.
'             Capture 2: 'one ' at 8.
'             Capture 3: 'sentence' at 12.
'       
'       Match: 'This is another.' at index 22.
'          Group 0: 'This is another.' at index 22.
'             Capture 0: 'This is another.' at 22.
'          Group 1: 'another' at index 30.
'             Capture 0: 'This ' at 22.
'             Capture 1: 'is ' at 27.
'             Capture 2: 'another' at 30.

Alt ifadeleri yalnızca niceleyicileri bunlara uygulamak için kullandığınızda ve yakalanan metinle ilgilenmediğinizde, grup yakalamalarını devre dışı bırakmanız gerekir. Örneğin, (?:subexpression) dil öğesi, uygulandığı grubun eşleşen alt dizeleri yakalamasını engeller. Aşağıdaki örnekte, önceki örnekteki normal ifade deseni olarak \b(?:\w+[;,]?\s?)+[.?!]değiştirilmiştir. Çıktıda gösterildiği gibi, normal ifade altyapısının ve CaptureCollection koleksiyonlarını doldurmasını GroupCollection engeller:

using System;
using System.Text.RegularExpressions;

public class Group2Example
{
    public static void Main()
    {
        string input = "This is one sentence. This is another.";
        string pattern = @"\b(?:\w+[;,]?\s?)+[.?!]";

        foreach (Match match in Regex.Matches(input, pattern))
        {
            Console.WriteLine("Match: '{0}' at index {1}.",
                              match.Value, match.Index);
            int grpCtr = 0;
            foreach (Group grp in match.Groups)
            {
                Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                                  grpCtr, grp.Value, grp.Index);
                int capCtr = 0;
                foreach (Capture cap in grp.Captures)
                {
                    Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                      capCtr, cap.Value, cap.Index);
                    capCtr++;
                }
                grpCtr++;
            }
            Console.WriteLine();
        }
    }
}
// The example displays the following output:
//       Match: 'This is one sentence.' at index 0.
//          Group 0: 'This is one sentence.' at index 0.
//             Capture 0: 'This is one sentence.' at 0.
//
//       Match: 'This is another.' at index 22.
//          Group 0: 'This is another.' at index 22.
//             Capture 0: 'This is another.' at 22.
Imports System.Text.RegularExpressions

Module Example
    Public Sub Main()
        Dim input As String = "This is one sentence. This is another."
        Dim pattern As String = "\b(?:\w+[;,]?\s?)+[.?!]"

        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("Match: '{0}' at index {1}.",
                              match.Value, match.Index)
            Dim grpCtr As Integer = 0
            For Each grp As Group In match.Groups
                Console.WriteLine("   Group {0}: '{1}' at index {2}.",
                                  grpCtr, grp.Value, grp.Index)
                Dim capCtr As Integer = 0
                For Each cap As Capture In grp.Captures
                    Console.WriteLine("      Capture {0}: '{1}' at {2}.",
                                      capCtr, cap.Value, cap.Index)
                    capCtr += 1
                Next
                grpCtr += 1
            Next
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays the following output:
'       Match: 'This is one sentence.' at index 0.
'          Group 0: 'This is one sentence.' at index 0.
'             Capture 0: 'This is one sentence.' at 0.
'       
'       Match: 'This is another.' at index 22.
'          Group 0: 'This is another.' at index 22.
'             Capture 0: 'This is another.' at 22.

Tutmayı, şu yöntemlerden biriyle devre dışı bırakabilirsiniz:

  • (?:subexpression) Dil öğesini kullanın. Bu öğe, geçerli olduğu gruptaki eşleşen alt dizelerin tutulmasını engeller. İç içe gruplardaki alt dize yakalamalarını devre dışı bırakmaz.

  • ExplicitCapture seçeneğini kullanın. Normal ifade deseninde tüm adlandırılmamış ya da örtük yakalamaları devre dışı bırakır. Bu seçeneği kullandığınızda, yalnızca dil öğesiyle tanımlanan adlandırılmış gruplarla (?<name>subexpression) eşleşen alt dizeler yakalanabilir. bayrağı, ExplicitCapture bir Regex sınıf oluşturucusunun options parametresine veya statik eşleştirme yönteminin parametresine options Regex geçirilebilir.

  • n dil öğesinde (?imnsx) seçeneğini kullanın. Bu seçenek, tutulan tüm adlandırılmamış veya örtük öğeleri, öğenin normal ifade deseninde ortaya çıktığı noktadan başlayarak devre dışı bırakır. Yakalamalar, desenin sonuna kadar veya seçenek adsız veya örtük yakalamaları etkinleştirene kadar (-n) devre dışı bırakılır. Daha fazla bilgi için bkz . Çeşitli Yapılar.

  • n dil öğesinde (?imnsx:subexpression) seçeneğini kullanın. Bu seçenek içindeki subexpressiontüm adsız veya örtük yakalamaları devre dışı bırakır. Yakalamalar adlandırılmamış ya da örtük yuvalı yakalama grupları tarafından devre dışı bırakılır.

İş parçacığı güvenliği

Sınıfın Regex kendisi iş parçacığı güvenli ve sabittir (salt okunur). Başka bir ifadeyle, Regex nesneler herhangi bir iş parçacığında oluşturulabilir ve iş parçacıkları arasında paylaşılabilir; eşleşen yöntemler herhangi bir iş parçacığından çağrılabilir ve hiçbir zaman genel durumu değiştirmez.

Ancak, tarafından Regex döndürülen sonuç nesneleri (Match ve MatchCollection) tek bir iş parçacığında kullanılmalıdır. Bu nesnelerin çoğu mantıksal olarak sabit olsa da, uygulamaları performansı geliştirmek için bazı sonuçların hesaplamasını geciktirebilir ve sonuç olarak çağıranların bunlara erişimi seri hale getirmesi gerekir.

Sonuç nesnelerini birden çok iş parçacığında paylaşmanız Regex gerekiyorsa, bu nesneler eşitlenmiş yöntemleri çağrılarak iş parçacığı açısından güvenli örneklere dönüştürülebilir. Numaralandırıcılar dışında, tüm normal ifade sınıfları iş parçacığı güvenlidir veya eşitlenmiş bir yöntemle iş parçacığı güvenli nesnelere dönüştürülebilir.

Numaralandırıcılar tek istisnadır. Koleksiyon numaralandırıcılarına çağrıları seri hale getirmeniz gerekir. Kural, bir koleksiyonun aynı anda birden fazla iş parçacığında numaralandırılabiliyorsa, numaralandırıcı tarafından geçiş yapılan koleksiyonun kök nesnesinde numaralandırıcı yöntemlerini eşitlemeniz gerekir.

Ünvan Açıklama
Normal İfade Davranışının Ayrıntıları .NET'te normal ifade altyapısının uygulanmasını inceler. Makale, normal ifadelerin esnekliğine odaklanır ve geliştiricinin normal ifade altyapısının verimli ve sağlam çalışmasını sağlama sorumluluğunu açıklar.
Geri Dönüş Geri izlemenin ne olduğunu ve bunun normal ifade performansını nasıl etkilediği açıklar ve geri izlemeye alternatifler sağlayan dil öğelerini inceler.
Normal İfade Dili - Hızlı Başvuru .NET'te normal ifade dilinin öğelerini açıklar ve her dil öğesi için ayrıntılı belgelere bağlantılar sağlar.