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

.NET'teki normal ifade motoru, metinleri sabit metinlerle karşılaştırmak ve eşleştirmek yerine, kalıp eşleşmelerine göre işleyen güçlü, tam özellikli bir araçtır. Çoğu durumda, desen eşleştirmeyi hızlı ve verimli bir şekilde gerçekleştirir. Ancak bazı durumlarda düzenli ifade motoru yavaş görünebilir. Aşırı durumlarda, saatler ve hatta günler boyunca nispeten küçük bir girişi işlediğinden 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şleme için System.Text.RegularExpressions kullanırken zaman aşımı değeri girin. Kötü niyetli bir kullanıcı RegularExpressions giriş sağlayarak bir Hizmet Reddi saldırısına yol açabilir. ASP.NET Core çerçeve API'leri zaman aşımı belirten RegularExpressions kullanır.

Güvenilen desenleri kullanma

.NET normal ifade altyapısı, desenlerin güvenilir olduğu, yani son kullanıcılar veya diğer güvenilmeyen kaynaklar tarafından sağlanmayan uygulama geliştiricisi tarafından yazıldıkları veya gözden geçirildikleri varsayımıyla tasarlanmıştır. Desenler, giriş metninden bağımsız olarak aşırı kaynak tüketimine neden olabilir ve normal ifade altyapısı düşman desenlere karşı korunmaya çalışmaz.

Uygulamanızın kullanıcılardan gelen arama ifadelerini kabul etmesi gerekiyorsa, kullanıcı girişini doğrudan bir regex deseni olarak geçirmekten kaçının. Bunun yerine şu alternatifleri göz önünde bulundurun:

  • Dahili olarak bir regex desenine çevirdiğiniz kısıtlı arama söz dizimlerini (basit joker karakterler veya alt dize eşleştirme gibi) destekleyin.
  • Kullanıcı tarafından sağlanan herhangi bir metni bir desen içindeki değişmez değer dizesi olarak işlemek için kullanın Regex.Escape .

Zaman aşımı değerleri ve RegexOptions.NonBacktracking (girişin uzunluğunda doğrusal zaman işlemeyi garanti eder) gibi özellikler, geliştirici tarafından yazılan desenlerdeki yanlışlıkla performans sorunlarına karşı korunmaya yardımcı olur. Bunlar kötü amaçlı desenlere karşı bir güvenlik sınırı olarak tasarlanmamıştır.

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

Genel olarak, normal ifadeler iki tür girişi kabul edebilir: kısıtlanmış veya kısıtlanmamış. 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. Yani, geliştiriciler eşleştirmek istedikleri metni inceler ve ardından bu metinle eşleşen normal bir ifade deseni yazar. Geliştiriciler daha sonra bu desenin düzeltme mi yoksa daha fazla detaylandırma mı gerektirdiğini birden çok geçerli giriş öğesiyle 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ış girdileri eşleştirmeye uygun değildir.

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

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

Son metin türü, kısıtlanmış girişi işlemek için yazılmış normal bir 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 izlemeye eğilimli ve geçerli e-posta adreslerini reddetme olasılığı olan bir düzenli 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, bir alfasayısal karakter ile başlayıp ardından alfasayısal, nokta veya kısa çizgi olabilecek sıfır veya daha fazla karakterden oluşur. Normal ifade alfasayısal 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öz ardı etme durumu, normal ifade desenine neredeyse uyan kısıtlanmamış girişlerin performansı önemli ölçüde düşürmesine izin verebilir.

Bu sorunu çözmek için aşağıdakileri yapabilirsiniz:

  • Bir desen geliştirirken, özellikle normal ifadeniz kısıtlanmamış girişleri işlemek için tasarlandıysa, geri izlemenin normal ifade altyapısının performansını nasıl etkileyebileceğini göz önünde bulundurmanız gerekir. Daha fazla bilgi için Geri İzlemenin Sorumluluğunu Alma bölümüne bakın.

  • Normal ifadenizi, geçersiz, geçerli olmaya yakın ve geçerli giriş kullanarak kapsamlı bir şekilde test edin. Rex'i kullanarak belirli bir normal ifade için rastgele giriş oluşturabilirsiniz. Rex , Microsoft Research tarafından sağlanan normal bir ifade araştırma aracıdır.

Nesne örneklemesini uygun şekilde işleme

NET'in normal ifade nesne modelinin merkezinde, normal ifade motorunu temsil eden System.Text.RegularExpressions.Regex sınıfı bulunur. Çoğunlukla, normal ifade performansını etkileyen en önemli faktör, Regex motorun kullanılma şeklidir. Normal ifade tanımlamak için normal ifade altyapısını normal ifade deseniyle sıkı bir şekilde bağlamanız gerekir. Bir Regex nesnesinin oluşturucusuna bir normal ifade deseni geçirerek bir nesneyi başlatmak veya normal ifade deseni ile analiz edilecek dizeyi geçirerek statik bir yöntem çağırarak nesneyi başlatmakla ilgili bu eşleme işlemi pahalıdır.

Uyarı

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. Regex nesnesi, options bayrağını içeren bir Compiled argümanı olmadan başlatıldığında sonuçlanır.

  • 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, Regex bayrağını içeren bir options bağımsız değişkenle Compiled nesnesi oluşturulduğunda derlenmiş desenleri temsil eder.

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, aynı normal ifadeye sahip bir normal ifade nesnesini tekrar tekrar örneklemeye 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 sık sık kullanıcı girişini doğrulamak için başka bir yöntem çağırır. Bu örnek, Button denetiminin Click olayı kullanılarak, kullanıcı tarafından en az bir ondalık basamakla birlikte bir para birimi simgesi girilip girilmediğini kontrol eden, IsValidCurrency adlı bir yöntemin çağrıldığı 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:

Uyarı

Her metot çağrısı, aynı desene sahip bir Regex nesnesini yeniden oluşturur. Bu da normal ifade deseninin yöntemi her çağrıldığında yeniden derlenmiş olması 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 son 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ış, kaynak tarafından üretilmiş ve derlenmiş normal ifadeler karşılaştırması

Compiled seçeneğinin belirtimi aracılığıyla normal ifade altyapısına bağlı olmayan normal ifade desenleri yorumlanır. Normal ifade nesnesi örneği oluşturulurken, 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. Yorumlanan normal ifadeler, başlangıç süresini azaltırken yürütme süresinin daha yavaş olmasına neden olur. 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ının sayısı arttıkça, daha düşük başlatma süresinden elde edilen performans artışı, daha yavaş yürütme hızı tarafından azaltılır.

Seçenek Compiled ile belirtilerek normal ifade motoruna bağlı olan desenler 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. Yorumlanan normal ifadelerin aksine, derlenmiş normal ifadeler başlatma süresini artırır ancak tek tek desen eşleştirme yöntemlerini daha hızlı yürütür. Sonuç olarak, normal ifadenin derlenmesinden elde edilen performans avantajı, çağrılan normal ifade yöntemlerinin sayısıyla orantılı olarak artar.

Regex özniteliği ile donatılmış GeneratedRegexAttribute döndüren bir yöntem aracılığıyla normal ifade motoruna bağlı olan normal ifade desenleri kaynak tarafından üretilir. Derleyiciye bağlanan kaynak oluşturucu, Regex'dan türetilmiş ve CIL'de RegexOptions.Compiled tarafından yayılan mantığa benzer olan bir özel uygulamayı C# kodu olarak yayar. RegexOptions.Compiled'nin tüm aktarım hızı performans avantajlarını (hatta daha fazlasını) ve Regex.CompileToAssembly'in başlangıçta elde edilen avantajlarını, ancak CompileToAssembly'nin karmaşıklığı olmadan 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 metotlarını belirli bir normal ifade ile nispeten seyrek çağırdığınızda, yorumlanan normal ifadeleri kullanın.
  • Düzenli ifadeleri nispeten sık kullanıyorsanız ve derleme zamanında bilinen bağımsız değişkenlerle C# kullanıyorsanız, kaynak tarafından oluşturulan düzenli 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ş normal ifade, çok sayıda çağrı yapıldığında (bu durumda 13.000'den fazla) daha iyi performans sunar.

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($"   {ctr} matches in {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($"   {ctr} matches in {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($"   {ctr} matches in {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($"   {matches:N0} matches in {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($"   {matches:N0} matches in {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($"   {matches:N0} matches in {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 Eşleşmeyi bir kelime sınırından başlatın.
\w+ Bir veya daha fazla sözcük karakteriyle eşleşir.
(\r?\n)|,?\s) Sıfır veya bir satır başı karakteri ve ardından bir yeni satır karakteri veya sıfır veya bir virgül ve ardından bir boşluk karakteri ile eşleşir.
(\w+((\r?\n)|,?\s))* Sıfır veya daha fazla oluşumla, bir veya daha fazla sözcük karakterinin ardından sıfır veya bir satır başı ve bir yeni satır karakteri ya da sıfır veya bir virgül ve sonra bir boşluk karakteri gelen durumları eşleştirir.
\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

Normalde normal ifade motoru, bir giriş dizesinde ilerlemek ve normal ifade deseni ile karşılaştırmak için doğrusal ilerleme kullanarak çalışır. Ancak, *, + ve ? gibi belirsiz niceleyiciler normal ifade deseninde kullanıldığında, normal ifade motoru başarılı kısmi eşleşmelerin bir bölümünden vazgeçebilir ve desenin tamamı için başarılı bir eşleşme aramak üzere daha önce kaydedilmiş bir duruma geri dönebilir. Bu işlem geri izleme olarak bilinir.

Tavsiye

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 izleme desteği normal ifadelere güç ve esneklik sağlar. Ayrıca normal ifade altyapısının çalışmasını denetleme sorumluluğunu normal ifade geliştiricilerin eline verir. Geliştiriciler genellikle bu sorumluluğun farkında olmadığından, geri izlemenin kötüye kullanılması veya aşırı geri izlenmeye bağlı olmaları genellikle normal ifade performansını düşürmede en önemli rol oynar. En kötü durumda yürütme süresi, giriş dizesindeki her ek karakter için ikiye katlanabilir. 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 Eşleşmeyi bir kelime sınırından başlatın.
\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şleşmeyi bir sözcük sınırında sonlandırın.

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. Normal ifade motoru, bir sözcük karakterinin her başarılı ön eşleşmesi için durumunu almak zorunda olduğundan, bu yalnızca performansı düşürür.

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

  • .NET 7'de kullanıma sunulan RegexOptions.NonBacktracking seçeneği ayarlandığında. Daha fazla bilgi için bkz. geri izlememe modu.

  • Atomik grup olarak bilinen (?>subexpression) dil öğesini kullanarak. Aşağıdaki örnek, iki normal ifade kullanarak bir giriş dizesini ayrıştırmaktadır. İlki, \b\p{Lu}\w*\b geri izlemeye dayanır. İkincisi, \b\p{Lu}(?>\w*)\b geri 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
    

Çoğu durumda, normal ifade deseni giriş metniyle eşleştirmek için geri izleme gereklidir. Ancak, aşırı geri izleme performansı ciddi ölçüde düşürebilir ve bir uygulamanın yanıt vermeyi durdurduğu izlenimi oluşturabilir. Ö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. Herhangi bir ek karakter, alfasayısal bir karakter, kısa çizgi, alt çizgi veya nokta içerebilir, ancak son karakter alfasayısal olmalıdır. 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 gibi durumlarda, iç içe niceleyicileri kaldırarak ve dış alt ifadeyi sıfır genişlikli bir ileriye veya geriye yönelik inceleme ifadesiyle değiştirerek normal ifade performansını iyileştirebilirsiniz. Lookahead ve lookbehind doğrulamaları yer işaretleridir. 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
^ Eşleşmeye giriş dizesinin başında başlayın.
[0-9A-Z] Alfasayısal bir karakterle eşleş. Parça numarası en azından bu karakterden oluşmalıdır.
[-.\w]* Herhangi bir sözcük karakterinin, kısa çizginin veya noktanın sıfır veya daha fazla oluşumunu eşleştirin.
\$ Dolar işaretini bul.
(?<=[0-9A-Z]) Sondaki dolar işaretinin arkasına bakarak önceki karakterin alfasayısal olduğundan emin olun.
$ Giriş dizesinin sonunda eşleşmeyi sonlandırın.

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 ileri bakış. Geçerli konumun önündeki konuma bakar ve subexpression giriş dizesiyle eşleşip eşleşmediğini belirler.
(?! subexpression ) Sıfır genişlikli negatif bakış. Geçerli konumun ilerisini kontrol ederek subexpression öğesinin giriş dizesiyle eşleşmediğini belirler.
(?<= subexpression ) Sıfır genişlikli pozitif arkaya bakma. Giriş dizesiyle eşleşip eşleşmediğini belirlemek amacıyla mevcut konumun arkasına bakar.
(?<! subexpression ) Sıfır genişlikli negatif görünüm. 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 desenine neredeyse uyan girişleri işlerse, genellikle aşırı geri izlemeden yararlanır ve bu da performansını ö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:               {info.Item1:N0}");
            Console.WriteLine($"Average Word Length: {info.Item2:N2} characters");
        }
        catch (IOException e)
        {
            Console.WriteLine($"IOException reading file '{title}'");
            Console.WriteLine(e.Message);
        }
        catch (RegexMatchTimeoutException e)
        {
            Console.WriteLine($"The operation timed out after {e.MatchTimeout.TotalMilliseconds:N0} milliseconds");
        }
    }
}

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 gruplama yapıları, sırayla numaralandırılmış bir yakalama grubunu tanımlayan alt ifade) ve adlandırılmış bir yakalama grubunu tanımlayan (?<> formundadır. Gruplandırma yapıları, geri başvurular oluşturmak ve niceleyicinin uygulandığı bir alt ifade tanımlamak için gereklidir.

Ancak, bu dil öğelerinin kullanımının bir maliyeti vardır. GroupCollection özelliği tarafından döndürülen nesnenin, en son adlandırılmamış veya adlandırılmış yakalamalarla doldurulmasına neden olurlar. Eğer tek bir gruplandırma yapısı giriş dizesinde birden çok alt dize yakalamışsa, bu alt dizeler, belirli bir yakalama grubunun CaptureCollection özelliği tarafından döndürülen Group.Captures nesnesini birden çok Capture nesneyle doldurur.

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

Desen Açıklama
\b Eşleşmeyi bir kelime sınırından başlatın.
\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 hem GroupCollection hem de CaptureCollection nesneleri eşleşmeden alınan yakalamalarla doldurulur. Bu durumda, yakalama grubu (\w+[;,]?\s?) vardır ki buna + niceleyici uygulanabilsin ve bu, normal ifade deseninin bir tümcedeki her sözcükle eşleşmesine olanak tanır. Aksi takdirde, tümcedeki son sözcükle eşleşer.

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: '{match.Value}' at index {match.Index}.");
            int grpCtr = 0;
            foreach (Group grp in match.Groups)
            {
                Console.WriteLine($"   Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
                int capCtr = 0;
                foreach (Capture cap in grp.Captures)
                {
                    Console.WriteLine($"      Capture {capCtr}: '{cap.Value}' at {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, GroupCollection ve CaptureCollection koleksiyonlarını doldurmaktan normal ifade motorunu 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: '{match.Value}' at index {match.Index}.");
            int grpCtr = 0;
            foreach (Group grp in match.Groups)
            {
                Console.WriteLine($"   Group {grpCtr}: '{grp.Value}' at index {grp.Index}.");
                int capCtr = 0;
                foreach (Capture cap in grp.Captures)
                {
                    Console.WriteLine($"      Capture {capCtr}: '{cap.Value}' at {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.

Yakalamaları aşağıdaki yollardan biriyle devre dışı bırakabilirsiniz:

  • (?:subexpression) Dil öğesini kullanın. Bu öğe, uygulandığı gruptaki eşleşen alt dizelerin yakalanmasını önler. İç içe gruplardaki alt dizgi yakalamalarını devre dışı bırakmaz.

  • ExplicitCapture seçeneğini kullanın. Normal ifade desenindeki tüm adsız veya ö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. bayrak, bir ExplicitCapture sınıf oluşturucusunun options parametresine veya bir Regex statik eşleştirme yönteminin options parametresine geçirilebilir.

  • n dil öğesinde (?imnsx) seçeneğini kullanın. Bu seçenek, öğenin göründüğü normal ifade desenindeki noktadan tüm adsız veya örtük yakalamaları devre dışı bırakır. Yakalamalar, ya desenin sonuna kadar ya da seçenek (-n) adsız veya örtük yakalamaları etkinleştirene kadar 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, subexpression içindeki tüm adsız veya örtük yakalamaları devre dışı bırakır. Herhangi bir adsız veya örtük iç içe yakalama grubu tarafından yapılan yakalamalar da devre dışı bırakılır.

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

Sınıf Regex kendisi iş parçacığı uyumlu ve değişmezdir (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, Match tarafından döndürülen sonuç nesneleri (MatchCollection ve Regex) 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 şudur: Eğer bir koleksiyon aynı anda birden fazla iş parçacığında numaralandırılabiliyorsa, numaralandırıcı tarafından geçilen koleksiyonun kök nesnesi üzerinde numaralandırıcı yöntemlerini eşitlemelisiniz.

Başlık 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 normal ifade performansını nasıl etkilediğini açıklar ve geri izleme için alternatifler sağlayan dil öğelerini inceler.
Düzenli İfade Dili - Hızlı Referans .NET'te normal ifade dilinin öğelerini açıklar ve her dil öğesi için ayrıntılı belgelere bağlantılar sağlar.