Normal ifadelerde geri izleme

Normal ifade deseni isteğe bağlı niceleyiciler veya değişim yapıları içerdiğinde ve normal ifade altyapısı eşleşme aramasına devam etmek için önceki kaydedilmiş duruma döndüğünde geri izleme gerçekleşir. Geri izleme, normal ifadelerin gücü bakımından çok önemlidir; ifadelerin güçlü ve esnek olmasına ve çok karmaşık desenlerle eşleşmelerine olanak sağlar. Aynı zamanda, bu güç bir maliyetle birlikte gelir. Geri izleme, genellikle normal ifade altyapısının performansını etkileyen tek önemli etmendir. Neyse ki, geliştirici, normal ifade motorunun davranışını ve geri izlemeyi nasıl kullandığını denetleyebilir. Bu konu, geri izlemenin nasıl çalıştığını ve nasıl kontrol edilebileceğini açıklar.

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.

Geri izleme olmadan doğrusal karşılaştırma

Bir normal ifade deseninin isteğe bağlı miktar niceleyicileri yoksa, normal ifade altyapısı doğrusal zamanda çalışır. Diğer bir deyişle, normal ifade altyapısı desendeki ilk dil öğesini giriş dizesindeki metinle eşleştirdikten sonra, it desende sonraki dil öğesini giriş dizesindeki sonraki karakterle veya karakter grubuyla eşleştirir. Bu, eşleştirme başarılı veya başarısız oluncaya kadar devam eder. Her iki durumda da, normal ifade altyapısı giriş dizesinde bir kerede bir karakter ilerler.

Aşağıdaki örnek, bir gösterim sağlar. Normal ifade e{2}\w\b , "e" harfinin iki tekrarını ve ardından herhangi bir sözcük karakterini ve ardından bir sözcük sınırını arar.

using System;
using System.Text.RegularExpressions;

public class Example1
{
    public static void Run()
    {
        string input = "needing a reed";
        string pattern = @"e{2}\w\b";
        foreach (Match match in Regex.Matches(input, pattern))
            Console.WriteLine("{0} found at position {1}",
                              match.Value, match.Index);
    }
}
// The example displays the following output:
//       eed found at position 11
Imports System.Text.RegularExpressions

Module Example1
    Public Sub Run()
        Dim input As String = "needing a reed"
        Dim pattern As String = "e{2}\w\b"
        For Each match As Match In Regex.Matches(input, pattern)
            Console.WriteLine("{0} found at position {1}",
                              match.Value, match.Index)
        Next
    End Sub
End Module
' The example displays the following output:
'       eed found at position 11

Bu normal ifade niceleyiciyi {2}içerse de, doğrusal bir şekilde değerlendirilir. Normal ifade altyapısı, isteğe bağlı bir niceleyici olmadığından geri dönmez {2} ; önceki alt ifadenin eşleşmesi gereken değişken sayısını değil, tam bir sayıyı belirtir. Sonuç olarak, normal ifade altyapısı, aşağıdaki tabloda gösterildiği gibi, normal ifade desenini giriş dizesiyle eşleştirmeye çalışır.

İşlem Desendeki konum Dizedeki konum Sonuç
1 e "needing a reed" (dizin 0) Eşleşme yok.
2 e "eeding a reed" (dizin 1) Olası eşleşme.
3 E{2} "eding a reed" (dizin 2) Olası eşleşme.
4 \w "al gerekli" (dizin 3) Olası eşleşme.
5 \b "ing a reed" (dizin 4) Olası eşleşme başarısız olur.
6 e "eding a reed" (dizin 2) Olası eşleşme.
7 E{2} "al gerekli" (dizin 3) Olası eşleşme başarısız olur.
8 e "al gerekli" (dizin 3) Eşleme başarısız olur.
9 e "ing a reed" (dizin 4) Eşleşme yok.
10 e "ng a reed" (dizin 5) Eşleşme yok.
11 e "g a reed" (dizin 6) Eşleşme yok.
12 e " a reed" (dizin 7) Eşleşme yok.
13 e "a reed" (dizin 8) Eşleşme yok.
14 e " reed" (dizin 9) Eşleşme yok.
15 e "reed" (dizin 10) Eşleşme yok
16 e "eed" (dizin 11) Olası eşleşme.
17 E{2} "ed" (dizin 12) Olası eşleşme.
18 \w "d" (dizin 13) Olası eşleşme.
19 \b "" (dizin 14) Eşleşme.

Bir normal ifade deseni isteğe bağlı miktar niceleyiciler veya değişim yapıları içermiyorsa, normal ifade desenini giriş dizesiyle eşleştirmek için gereken en fazla karşılaştırma sayısı, kabaca giriş dizesindeki karakter sayısına eşittir. Bu durumda, normal ifade altyapısı, 13 karakterlik bu dizedeki olası eşleşmeleri tanımlamak için 19 karşılaştırma kullanır. Diğer bir deyişle, isteğe bağlı miktar niceleyiciler veya değişim yapıları içermiyorsa, normal ifade altyapısı doğrusala yakın bir zamanda çalışır.

İsteğe bağlı niceleyiciler veya değişim yapıları ile geri izleme

Normal bir ifade isteğe bağlı miktar niceleyiciler veya değişim yapıları içerdiğinde, giriş dizesinin değerlendirilmesi artık doğrusal değildir. Nondeterministic Finite Automaton (NFA) altyapısıyla desen eşleştirme, giriş dizesinde eşleştirilecek karakterler tarafından değil normal ifadedeki dil öğeleri tarafından yönlendirilir. Bu nedenle, normal ifade altyapısı, isteğe bağlı veya alternatif alt ifadeleri tam olarak eşleştirmeye çalışır. Alt ifadede sonraki dil öğesine ilerlediğinde ve eşleştirme başarısız olduğunda, normal ifade altyapısı, normal ifadeyi giriş dizesiyle bir bütün olarak eşleştirmek amacıyla, başarılı eşleştirmesinin bir bölümünü bırakır ve daha önce kaydedilen bir duruma geri döner. Bir eşleştirme bulmak üzere daha önce kaydedilen bir duruma bu şekilde geri dönme işlemi, geri izleme olarak bilinir.

Örneğin, "es" karakterleriyle ve ondan önceki tüm karakterlerle eşleşen normal ifade desenini .*(es)göz önünde bulundurun. Aşağıdaki örnekte gösterildiği gibi, giriş dizesi "Essential services are provided by regular expressions." ise, desen, "expressions"daki "es"a kadar ve "es" dahil olmak üzere tüm dizeyle eşleşir.

using System;
using System.Text.RegularExpressions;

public class Example2
{
    public static void Run()
    {
        string input = "Essential services are provided by regular expressions.";
        string pattern = ".*(es)";
        Match m = Regex.Match(input, pattern, RegexOptions.IgnoreCase);
        if (m.Success)
        {
            Console.WriteLine("'{0}' found at position {1}",
                              m.Value, m.Index);
            Console.WriteLine("'es' found at position {0}",
                              m.Groups[1].Index);
        }
    }
}
//    'Essential services are provided by regular expressions found at position 0
//    'es' found at position 47
Imports System.Text.RegularExpressions

Module Example2
    Public Sub Run()
        Dim input As String = "Essential services are provided by regular expressions."
        Dim pattern As String = ".*(es)"
        Dim m As Match = Regex.Match(input, pattern, RegexOptions.IgnoreCase)
        If m.Success Then
            Console.WriteLine("'{0}' found at position {1}",
                              m.Value, m.Index)
            Console.WriteLine("'es' found at position {0}",
                              m.Groups(1).Index)
        End If
    End Sub
End Module
'    'Essential services are provided by regular expressions found at position 0
'    'es' found at position 47

Bunu yapmak için, normal ifade altyapısı aşağıdaki gibi geri izlemeyi kullanır:

  • Giriş dizesinin tamamıyla (herhangi bir karakterin .* sıfır, bir veya daha fazla yinelemesiyle eşleşen) ile eşleşir.

  • Normal ifade deseninde "e"yi eşleştirmeyi dener. Ancak, giriş dizesinde eşleştirilebilecek başka karakter kalmamıştır.

  • En son başarılı eşleştirmesi olan "Essential services are provided by regular expressions" dizesine geri izleme yapar ve "e"yi cümlenin sonundaki nokta ile eşleştirmeyi dener. Eşleştirme başarısız olur.

  • Geçici olarak eşleştirilen "Essential services are provided by regular expr" alt dizesine kadar, bir kerede bir karakter olacak şekilde, daha önceki başarılı bir eşleştirmeye geri izleme yapmaya devam eder. Ardından, desendeki "e"yi "expressions"daki ikinci "e" ile karşılaştırır ve bir eşleştirme bulur.

  • Desendeki "s" ile, eşleştirilen "e" karakterini izleyen "s"yi ("expressions"daki ilk "s") karşılaştırır. Eşleştirme başarılıdır.

Geri izleme kullandığınızda, normal ifade desenini 55 karakter uzunluğundaki giriş dizesiyle eşleştirmek, 67 karşılaştırma işlemi gerektirir. Genellikle, normal bir ifade deseninin tek bir değişim yapısı veya tek bir isteğe bağlı miktar niceleyicisi varsa, deseni eşleştirmek için gereken karşılaştırma işlemlerinin sayısı, giriş dizesindeki karakterlerin sayısının iki katıdır.

İç içe isteğe bağlı niceleyicilerle geri izleme

Desen çok sayıda değişim yapıları içeriyorsa, iç içe değişim yapıları içeriyorsa veya en yaygın olasılık olarak iç içe isteğe bağlı miktar niceleyiciler içeriyorsa, normal bir ifade desenini eşleştirmek için gereken karşılaştırma işlemlerinin sayısı katlanarak artabilir. Örneğin, normal ifade deseni ^(a+)+$ bir veya daha fazla "a" karakteri içeren tam bir dizeyle eşleşecek şekilde tasarlanmıştır. Örnek, aynı uzunlukta iki giriş dizesi sağlar, fakat yalnızca ilk dize desenle eşleşir. sınıfı System.Diagnostics.Stopwatch , eşleştirme işleminin ne kadar sürdüğünü belirlemek için kullanılır.

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

public class Example3
{
    public static void Run()
    {
        string pattern = "^(a+)+$";
        string[] inputs = { "aaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaa!" };
        Regex rgx = new Regex(pattern);
        Stopwatch sw;

        foreach (string input in inputs)
        {
            sw = Stopwatch.StartNew();
            Match match = rgx.Match(input);
            sw.Stop();
            if (match.Success)
                Console.WriteLine($"Matched {match.Value} in {sw.Elapsed}");
            else
                Console.WriteLine($"No match found in {sw.Elapsed}");
        }
    }
}
//    Matched aaaaaaaaaaaaaaaaaaaaaaaaaaa in 00:00:00.0018281
//    No match found in 00:00:05.1882144
Imports System.Text.RegularExpressions

Module Example3
    Public Sub Run()
        Dim pattern As String = "^(a+)+$"
        Dim inputs() As String = {"aaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaa!"}
        Dim rgx As New Regex(pattern)
        Dim sw As Stopwatch

        For Each input As String In inputs
            sw = Stopwatch.StartNew()
            Dim match As Match = rgx.Match(input)
            sw.Stop()
            If match.Success Then
                Console.WriteLine("Matched {0} in {1}", match.Value, sw.Elapsed)
            Else
                Console.WriteLine("No match found in {0}", sw.Elapsed)
            End If
        Next
    End Sub
End Module
'    Matched aaaaaaaaaaaaaaaaaaaaaaaaaaa in 00:00:00.0018281
'    No match found in 00:00:05.1882144

Örnekteki çıktıda gösterildiği gibi, normal ifade altyapısının bir giriş dizesinin eşleşen bir dizeyi tanımlamak için olduğu gibi desenle eşleşmediğini bulması önemli ölçüde uzun sürdü. Bunun nedeni, başarısız bir eşleştirmenin her zaman bir kötü durum senaryosunu temsil etmesidir. Eşleştirmenin başarısız olduğu sonucuna varabilmesinden ve iç içe parantezlerin veri içinde birçok ek yol oluşturmasından önce, normal ifade altyapısının veri içinde olası tüm yolları izlemek için normal ifadeyi kullanması gerekir. Normal ifade altyapısı, aşağıdakileri yaparak ikinci dizenin desenle eşleşmediği sonucuna varır:

  • Dizenin başında olup olmadığını denetler ve ardından dizedeki ilk beş karakteri deseniyle a+eşleştirir. Ardından, dizede ek "a" karakteri grupları olmadığını belirler. Son olarak, dizenin sonu için sınama yapar. Dizede bir ek karakter kaldığından, eşleştirme başarısız olur. Bu hatalı eşleşme, 9 karşılaştırma gerektirir. Normal ifade altyapısı ayrıca "a" (eşleşme 1), "aa" (eşleşme 2), "aaa" (eşleşme 3) ve "aa" (eşleşme 4) eşleşmelerinden durum bilgilerini kaydeder.

  • Önceden kaydedilen eşleştirme 4'e döndürür. Ek bir yakalan gruba atamak için bir ek "a" karakteri olduğunu belirler. Son olarak, dizenin sonu için sınama yapar. Dizede bir ek karakter kaldığından, eşleştirme başarısız olur. Bu başarısız eşleşme için 4 karşılaştırma gerekir. Şu ana kadar toplam 13 karşılaştırma yapıldı.

  • Daha önce kaydedilen 3. eşleşmeye döner. Ek bir yakalanan gruba atamak için iki ek "a" karakteri olduğunu belirler. Ancak, dize sonu sınaması başarısız olur. Ardından 3 ile eşleşecek şekilde döner ve yakalanan iki ek gruptaki iki ek "a" karakterini eşleştirmeye çalışır. Dize sonu sınaması hala başarısız olur. Bu başarısız eşleştirmeler 12 karşılaştırma gerektirir. Şimdiye kadar toplam 25 karşılaştırma gerçekleştirildi.

Giriş dizesinin normal ifadeyle karşılaştırılması, normal ifade altyapısı tüm olası eşleştirme birleşimlerini deneyinceye kadar bu şekilde devam eder ve ardından eşleştirme olmadığı sonucuna ulaşır. İç içe niceleyiciler nedeniyle, bu karşılaştırma bir O(2n) veya üstel bir işlemdir; burada n , giriş dizesindeki karakter sayısıdır. Bu, en kötü durumda, 30 karakterlik bir giriş dizesinin yaklaşık 1.073.741.824 karşılaştırma gerektirdiği ve 40 karakterlik bir giriş dizesinin yaklaşık 1,099,511,627,776 karşılaştırma gerektirdiği anlamına gelir. Bu uzunluklarda veya daha uzun dizeler kullanırsanız, normal ifade deseniyle eşleşmeyen giriş işlediklerinde, normal ifade yöntemlerinin tamamlanması çok uzun zaman alabilir.

Denetim geri izleme

Geri izleme, güçlü ve esnek normal ifadeler oluşturmanıza olanak tanır. Ancak, önceki bölümde gösterildiği gibi, bu yararlar kabuk edilemeyecek kadar düşük performansla eşleştirilebilir. Aşırı geri izlemeyi önlemek için, bir nesne örneği Regex oluştururken veya statik normal ifade eşleştirme yöntemini çağırırken zaman aşımı aralığı tanımlamanız gerekir. Bu konu, sonraki bölümde açıklanmaktadır. Buna ek olarak, .NET geri izlemeyi sınırlayan veya engelleyen ve çok az performans cezası olan veya hiç performans cezası olmayan karmaşık normal ifadeleri destekleyen üç normal ifade dili öğesini destekler: atomik gruplar, lookbehind onayları ve lookahead onayları. Her dil öğesi hakkında daha fazla bilgi için bkz . Yapıları gruplandırma.

Normal ifade altyapısında geri izlenmeyen

Geri izleme gerektiren herhangi bir yapı (örneğin, aramalar, geri başvurular veya atomik gruplar) kullanmanız gerekmiyorsa modu kullanmayı RegexOptions.NonBacktracking göz önünde bulundurun. Bu mod, girişin uzunluğuyla orantılı olarak zaman içinde yürütülecek şekilde tasarlanmıştır. Daha fazla bilgi için bkz . NonBacktracking modu. Zaman aşımı değeri de ayarlayabilirsiniz.

Girişlerin boyutunu sınırlama

Giriş son derece büyük olmadığı sürece bazı normal ifadeler kabul edilebilir performansa sahiptir. Senaryonuzdaki tüm makul metin girişlerinin belirli bir uzunlukta olduğu biliniyorsa, normal ifadeyi uygulamadan önce daha uzun girişleri reddetmeyi göz önünde bulundurun.

Zaman aşımı aralığı belirtme

Normal ifade altyapısının denemeyi bırakmadan ve özel durum oluşturmadan önce tek bir eşleşmeyi arayacağı en uzun aralığı temsil eden bir RegexMatchTimeoutException zaman aşımı değeri ayarlayabilirsiniz. Oluşturucuya normal ifadeler gibi bir TimeSpan değer Regex(String, RegexOptions, TimeSpan) sağlayarak zaman aşımı aralığını belirtirsiniz. Buna ek olarak, her statik desen eşleştirme yönteminin bir zaman aşımı değeri belirtmenize olanak tanıyan bir TimeSpan parametresi olan bir aşırı yüklemesi vardır.

Zaman aşımı değerini açıkça ayarlamazsanız, varsayılan zaman aşımı değeri aşağıdaki gibi belirlenir:

  • Varsa, uygulama genelinde zaman aşımı değerini kullanarak. Bu, nesnenin örneği oluşturulduğu veya statik yöntem çağrısının yapıldığı Regex uygulama etki alanına uygulanan herhangi bir zaman aşımı değeri olabilir. Bir değerin dize gösterimini TimeSpan "REGEX_DEFAULT_MATCH_TIMEOUT" özelliğine atamak için yöntemini çağırarak AppDomain.SetData uygulama genelinde zaman aşımı değerini ayarlayabilirsiniz.
  • uygulama genelinde zaman aşımı değeri InfiniteMatchTimeoutayarlanmamışsa değerini kullanarak.

Varsayılan olarak, zaman aşımı aralığı olarak ayarlanır Regex.InfiniteMatchTimeout ve normal ifade altyapısı zaman aşımına neden olmaz.

Önemli

kullanmadığınızda RegexOptions.NonBacktracking, normal ifadeniz geri izlemeyi kullanıyorsa veya güvenilmeyen girişlerde çalıştırıldığında her zaman bir zaman aşımı aralığı ayarlamanızı öneririz.

Özel RegexMatchTimeoutException durum, normal ifade altyapısının belirtilen zaman aşımı aralığı içinde eşleşme bulamadığını ancak özel durumun neden oluşturulduğuna işaret etmediğini gösterir. Bunun nedeni aşırı geri izleme olabilir, ancak özel durumun oluştuğu sırada sistem yükü dikkate alındığında zaman aşımı aralığının çok düşük ayarlanmış olması da mümkündür. Özel durumu işlediğinizde, giriş dizesiyle diğer eşleştirmeleri bırakmayı veya zaman aşımı aralığını artırarak eşleştirme işlemini yeniden denemeyi seçebilirsiniz.

Örneğin, aşağıdaki kod 1 saniyelik zaman aşımı değerine sahip bir Regex nesnenin örneğini oluşturmak için oluşturucuyu çağırırRegex(String, RegexOptions, TimeSpan). Bir satırın sonundaki bir veya daha fazla "a" karakterinin bir veya daha fazla dizisiyle eşleşen normal ifade deseni (a+)+$aşırı geri izlenmeye tabidir. oluşturulursa RegexMatchTimeoutException , örnek zaman aşımı değerini en fazla 3 saniyelik bir araya kadar artırır. Bundan sonra, deseni eşleştirme girişiminden vazgeçer.

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Security;
using System.Text.RegularExpressions;
using System.Threading;

public class Example
{
    const int MaxTimeoutInSeconds = 3;

    public static void Main()
    {
        string pattern = @"(a+)+$";    // DO NOT REUSE THIS PATTERN.
        Regex rgx = new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
        Stopwatch? sw = null;

        string[] inputs = { "aa", "aaaa>",
                         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                         "aaaaaaaaaaaaaaaaaaaaaa>",
                         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>" };

        foreach (var inputValue in inputs)
        {
            Console.WriteLine("Processing {0}", inputValue);
            bool timedOut = false;
            do
            {
                try
                {
                    sw = Stopwatch.StartNew();
                    // Display the result.
                    if (rgx.IsMatch(inputValue))
                    {
                        sw.Stop();
                        Console.WriteLine(@"Valid: '{0}' ({1:ss\.fffffff} seconds)",
                                          inputValue, sw.Elapsed);
                    }
                    else
                    {
                        sw.Stop();
                        Console.WriteLine(@"'{0}' is not a valid string. ({1:ss\.fffff} seconds)",
                                          inputValue, sw.Elapsed);
                    }
                }
                catch (RegexMatchTimeoutException e)
                {
                    sw.Stop();
                    // Display the elapsed time until the exception.
                    Console.WriteLine(@"Timeout with '{0}' after {1:ss\.fffff}",
                                      inputValue, sw.Elapsed);
                    Thread.Sleep(1500);       // Pause for 1.5 seconds.

                    // Increase the timeout interval and retry.
                    TimeSpan timeout = e.MatchTimeout.Add(TimeSpan.FromSeconds(1));
                    if (timeout.TotalSeconds > MaxTimeoutInSeconds)
                    {
                        Console.WriteLine("Maximum timeout interval of {0} seconds exceeded.",
                                          MaxTimeoutInSeconds);
                        timedOut = false;
                    }
                    else
                    {
                        Console.WriteLine("Changing the timeout interval to {0}",
                                          timeout);
                        rgx = new Regex(pattern, RegexOptions.IgnoreCase, timeout);
                        timedOut = true;
                    }
                }
            } while (timedOut);
            Console.WriteLine();
        }
    }
}
// The example displays output like the following :
//    Processing aa
//    Valid: 'aa' (00.0000779 seconds)
//
//    Processing aaaa>
//    'aaaa>' is not a valid string. (00.00005 seconds)
//
//    Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
//    Valid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' (00.0000043 seconds)
//
//    Processing aaaaaaaaaaaaaaaaaaaaaa>
//    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 01.00469
//    Changing the timeout interval to 00:00:02
//    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 02.01202
//    Changing the timeout interval to 00:00:03
//    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 03.01043
//    Maximum timeout interval of 3 seconds exceeded.
//
//    Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
//    Timeout with 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>' after 03.01018
//    Maximum timeout interval of 3 seconds exceeded.
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Security
Imports System.Text.RegularExpressions
Imports System.Threading

Module Example
    Const MaxTimeoutInSeconds As Integer = 3

    Public Sub Main()
        Dim pattern As String = "(a+)+$"    ' DO NOT REUSE THIS PATTERN.
        Dim rgx As New Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1))
        Dim sw As Stopwatch = Nothing

        Dim inputs() As String = {"aa", "aaaa>",
                                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
                                   "aaaaaaaaaaaaaaaaaaaaaa>",
                                   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>"}

        For Each inputValue In inputs
            Console.WriteLine("Processing {0}", inputValue)
            Dim timedOut As Boolean = False
            Do
                Try
                    sw = Stopwatch.StartNew()
                    ' Display the result.
                    If rgx.IsMatch(inputValue) Then
                        sw.Stop()
                        Console.WriteLine("Valid: '{0}' ({1:ss\.fffffff} seconds)",
                                          inputValue, sw.Elapsed)
                    Else
                        sw.Stop()
                        Console.WriteLine("'{0}' is not a valid string. ({1:ss\.fffff} seconds)",
                                          inputValue, sw.Elapsed)
                    End If
                Catch e As RegexMatchTimeoutException
                    sw.Stop()
                    ' Display the elapsed time until the exception.
                    Console.WriteLine("Timeout with '{0}' after {1:ss\.fffff}",
                                      inputValue, sw.Elapsed)
                    Thread.Sleep(1500)       ' Pause for 1.5 seconds.

                    ' Increase the timeout interval and retry.
                    Dim timeout As TimeSpan = e.MatchTimeout.Add(TimeSpan.FromSeconds(1))
                    If timeout.TotalSeconds > MaxTimeoutInSeconds Then
                        Console.WriteLine("Maximum timeout interval of {0} seconds exceeded.",
                                          MaxTimeoutInSeconds)
                        timedOut = False
                    Else
                        Console.WriteLine("Changing the timeout interval to {0}",
                                          timeout)
                        rgx = New Regex(pattern, RegexOptions.IgnoreCase, timeout)
                        timedOut = True
                    End If
                End Try
            Loop While timedOut
            Console.WriteLine()
        Next
    End Sub
End Module
' The example displays output like the following:
'    Processing aa
'    Valid: 'aa' (00.0000779 seconds)
'    
'    Processing aaaa>
'    'aaaa>' is not a valid string. (00.00005 seconds)
'    
'    Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
'    Valid: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' (00.0000043 seconds)
'    
'    Processing aaaaaaaaaaaaaaaaaaaaaa>
'    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 01.00469
'    Changing the timeout interval to 00:00:02
'    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 02.01202
'    Changing the timeout interval to 00:00:03
'    Timeout with 'aaaaaaaaaaaaaaaaaaaaaa>' after 03.01043
'    Maximum timeout interval of 3 seconds exceeded.
'    
'    Processing aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>
'    Timeout with 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa>' after 03.01018
'    Maximum timeout interval of 3 seconds exceeded.

Atomik gruplar

Alt (?>ifade) dili öğesi atomik bir gruplandırmadır. Alt ifadeye geri izlemeyi engeller. Bu dil öğesi başarıyla eşleştirildikten sonra, sonraki geri izlemeyle eşleşmesinin hiçbir bölümünden vazgeçmez. Örneğin, deseninde (?>\w*\d*)1, eşleştirilemiyorsa 1 , \d* eşleşmenin başarıyla eşleşmesine izin vereceği anlamına gelse bile eşleşmesinin 1 hiçbirini bırakmaz. Atomik gruplar, başarısız eşleşmelerle ilişkili performans sorunlarını önlemeye yardımcı olabilir.

Aşağıdaki örnekte, iç içe miktar niceleyiciler kullanılırken geri izlemenin bastırılmasının performansı nasıl iyileştirdiği gösterilmektedir. Normal ifade altyapısının bir giriş dizesinin iki normal ifadeyle eşleşmediğini belirlemesi için gereken süreyi ölçer. İlk normal ifade, ardından bir iki nokta işareti, ardından bir veya daha fazla ondalık basamak, ardından iki iki nokta işareti gelen bir veya birden fazla ondalık basamağın bir veya birden fazla örneğini içeren bir dizeyle eşleştirme yapmayı denemek için geri izleme kullanır. İkinci normal ifade, geri izlemeyi devre dışı bırakması dışında, birincisiyle aynıdır. Örnekteki çıktının gösterdiği gibi, geri izlemeyi devre dışı bırakmanın sağladığı performans iyileşmesi önemlidir.

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

public class Example4
{
    public static void Run()
    {
        string input = "b51:4:1DB:9EE1:5:27d60:f44:D4:cd:E:5:0A5:4a:D24:41Ad:";
        bool matched;
        Stopwatch sw;

        Console.WriteLine("With backtracking:");
        string backPattern = "^(([0-9a-fA-F]{1,4}:)*([0-9a-fA-F]{1,4}))*(::)$";
        sw = Stopwatch.StartNew();
        matched = Regex.IsMatch(input, backPattern);
        sw.Stop();
        Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, backPattern), sw.Elapsed);
        Console.WriteLine();

        Console.WriteLine("Without backtracking:");
        string noBackPattern = "^((?>[0-9a-fA-F]{1,4}:)*(?>[0-9a-fA-F]{1,4}))*(::)$";
        sw = Stopwatch.StartNew();
        matched = Regex.IsMatch(input, noBackPattern);
        sw.Stop();
        Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, noBackPattern), sw.Elapsed);
    }
}
// The example displays output like the following:
//       With backtracking:
//       Match: False in 00:00:27.4282019
//
//       Without backtracking:
//       Match: False in 00:00:00.0001391
Imports System.Text.RegularExpressions

Module Example4
    Public Sub Run()
        Dim input As String = "b51:4:1DB:9EE1:5:27d60:f44:D4:cd:E:5:0A5:4a:D24:41Ad:"
        Dim matched As Boolean
        Dim sw As Stopwatch

        Console.WriteLine("With backtracking:")
        Dim backPattern As String = "^(([0-9a-fA-F]{1,4}:)*([0-9a-fA-F]{1,4}))*(::)$"
        sw = Stopwatch.StartNew()
        matched = Regex.IsMatch(input, backPattern)
        sw.Stop()
        Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, backPattern), sw.Elapsed)
        Console.WriteLine()

        Console.WriteLine("Without backtracking:")
        Dim noBackPattern As String = "^((?>[0-9a-fA-F]{1,4}:)*(?>[0-9a-fA-F]{1,4}))*(::)$"
        sw = Stopwatch.StartNew()
        matched = Regex.IsMatch(input, noBackPattern)
        sw.Stop()
        Console.WriteLine("Match: {0} in {1}", Regex.IsMatch(input, noBackPattern), sw.Elapsed)
    End Sub
End Module
' The example displays the following output:
'       With backtracking:
'       Match: False in 00:00:27.4282019
'       
'       Without backtracking:
'       Match: False in 00:00:00.0001391

Lookbehind onayları

.NET, (?<=giriş dizesindeki önceki karakter veya karakterlerle eşleşen iki dil öğesi (alt) ifade ve (?<!alt ifade)) içerir. Her iki dil öğesi de sıfır genişlikli onaylardır; yani, geçerli karakterden hemen önce gelen karakterin veya karakterlerin, ilerleme veya geri izleme olmadan alt ifadeyle eşleştirilip eşleştirilemeyeceğini belirler.

(?<=subexpression) pozitif bir lookbehind onaylama işlemidir; yani geçerli konumdan önceki karakter veya karakterler alt ifadeyle eşleşmelidir. (?<!alt ifade) negatif bir lookbehind onaylama işlemidir; yani geçerli konumdan önceki karakter veya karakterler alt ifadeyle eşleşmemelidir. Alt ifade önceki alt ifadenin bir alt kümesi olduğunda hem pozitif hem de negatif lookbehind onayları en çok yararlıdır.

Aşağıdaki örnekte, e-posta adresinde kullanıcı adını doğrulayan iki eşdeğer normal ifade deseni kullanılmaktadır. Birinci desen, aşırı geri izleme nedeniyle yetersiz performansa maruz kalır. İkinci desen, iç içe bir miktar niceleyiciyi pozitif bir geriye yönelik onayla değiştirerek birinci normal ifadeyi değiştirir. Örnekten alınan çıkış, yönteminin Regex.IsMatch yürütme zamanını görüntüler.

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

public class Example5
{
    public static void Run()
    {
        Stopwatch sw;
        string input = "test@contoso.com";
        bool result;

        string pattern = @"^[0-9A-Z]([-.\w]*[0-9A-Z])?@";
        sw = Stopwatch.StartNew();
        result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
        sw.Stop();
        Console.WriteLine("Match: {0} in {1}", result, sw.Elapsed);

        string behindPattern = @"^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@";
        sw = Stopwatch.StartNew();
        result = Regex.IsMatch(input, behindPattern, RegexOptions.IgnoreCase);
        sw.Stop();
        Console.WriteLine("Match with Lookbehind: {0} in {1}", result, sw.Elapsed);
    }
}
// The example displays output similar to the following:
//       Match: True in 00:00:00.0017549
//       Match with Lookbehind: True in 00:00:00.0000659
Module Example5
    Public Sub Run()
        Dim sw As Stopwatch
        Dim input As String = "test@contoso.com"
        Dim result As Boolean

        Dim pattern As String = "^[0-9A-Z]([-.\w]*[0-9A-Z])?@"
        sw = Stopwatch.StartNew()
        result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)
        sw.Stop()
        Console.WriteLine("Match: {0} in {1}", result, sw.Elapsed)

        Dim behindPattern As String = "^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@"
        sw = Stopwatch.StartNew()
        result = Regex.IsMatch(input, behindPattern, RegexOptions.IgnoreCase)
        sw.Stop()
        Console.WriteLine("Match with Lookbehind: {0} in {1}", result, sw.Elapsed)
    End Sub
End Module
' The example displays output similar to the following:
'       Match: True in 00:00:00.0017549
'       Match with Lookbehind: True in 00:00:00.0000659

İlk normal ifade deseni, ^[0-9A-Z]([-.\w]*[0-9A-Z])*@aşağıdaki tabloda gösterildiği gibi tanımlanır.

Desen Açıklama
^ Eşleştirmeyi dizenin başlangıcında başlatın.
[0-9A-Z] Alfasayısal bir karakterle eşleştirin Yöntemi seçeneğiyle RegexOptions.IgnoreCase çağrıldığından Regex.IsMatch bu karşılaştırma büyük/küçük harfe duyarlı değildir.
[-.\w]* Bir kısa çizgi, nokta veya sözcük karakterinin sıfır, bir veya daha fazla örneğini eşleştirin.
[0-9A-Z] Alfasayısal bir karakterle eşleştirin
([-.\w]*[0-9A-Z])* Ardından alfasayısal bir karakter gelen sıfır veya daha fazla kısa çizgi, nokta veya sözcük karakteri birleşiminin sıfır veya daha fazla örneğini eşleştirin. Bu ilk yakalama grubudur.
@ Bir at işaretiyle ("@") eşleştirin.

İkinci normal ifade deseni olan ^[0-9A-Z][-.\w]*(?<=[0-9A-Z])@, pozitif bir lookbehind onayı kullanır. Aşağıdaki tabloda gösterildiği gibi tanımlanmıştır.

Desen Açıklama
^ Eşleştirmeyi dizenin başlangıcında başlatın.
[0-9A-Z] Alfasayısal bir karakterle eşleştirin Yöntemi seçeneğiyle RegexOptions.IgnoreCase çağrıldığından Regex.IsMatch bu karşılaştırma büyük/küçük harfe duyarlı değildir.
[-.\w]* Bir kısa çizgi, nokta veya sözcük karakterinin sıfır, bir veya daha fazla örneğini eşleştirin.
(?<=[0-9A-Z]) Son eşleşen karaktere geriye doğru bakın ve alfasayısal ise eşleştirmeyi devam ettirin. Alfasayısal karakterlerin, nokta, kısa çizgi ve tüm sözcük karakterlerinden oluşan kümenin bir alt kümesi olduğunu unutmayın.
@ Bir at işaretiyle ("@") eşleştirin.

Lookahead onayları

.NET, (?=giriş dizesindeki bir sonraki karakter veya karakterlerle eşleşen iki dil öğesi (alt) ifade ve (?!alt ifade)) içerir. Her iki dil öğesi de sıfır genişlikli onaylardır; yani, geçerli karakteri hemen izleyen karakterin veya karakterlerin, ilerleme veya geri izleme olmadan alt ifadeyle eşleştirilip eşleştirilemeyeceğini belirler.

(?=alt ifade) pozitif bir lookahead onayıdır; yani geçerli konumdan sonraki karakter veya karakterler alt ifadeyle eşleşmelidir. (?!alt ifade) negatif bir lookahead onayıdır; yani geçerli konumdan sonraki karakter veya karakterler alt ifadeyle eşleşmemelidir. Hem pozitif hem de negatif lookahead onayları en çok alt ifade bir sonraki alt ifadenin alt kümesi olduğunda yararlıdır.

Aşağıdaki örnekte, tam olarak belirtilen bir tür adını doğrulayan iki denk normal ifade deseni kullanılmaktadır. Birinci desen, aşırı geri izleme nedeniyle yetersiz performansa maruz kalır. İkincisi, iç içe bir miktar niceleyiciyi pozitif bir ileriye yönelik onayla değiştirerek birinci normal ifadeyi değiştirir. Örnekten alınan çıkış, yönteminin Regex.IsMatch yürütme zamanını görüntüler.

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

public class Example6
{
    public static void Run()
    {
        string input = "aaaaaaaaaaaaaaaaaaaaaa.";
        bool result;
        Stopwatch sw;

        string pattern = @"^(([A-Z]\w*)+\.)*[A-Z]\w*$";
        sw = Stopwatch.StartNew();
        result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase);
        sw.Stop();
        Console.WriteLine("{0} in {1}", result, sw.Elapsed);

        string aheadPattern = @"^((?=[A-Z])\w+\.)*[A-Z]\w*$";
        sw = Stopwatch.StartNew();
        result = Regex.IsMatch(input, aheadPattern, RegexOptions.IgnoreCase);
        sw.Stop();
        Console.WriteLine("{0} in {1}", result, sw.Elapsed);
    }
}
// The example displays the following output:
//       False in 00:00:03.8003793
//       False in 00:00:00.0000866
Imports System.Text.RegularExpressions

Module Example6
    Public Sub Run()
        Dim input As String = "aaaaaaaaaaaaaaaaaaaaaa."
        Dim result As Boolean
        Dim sw As Stopwatch

        Dim pattern As String = "^(([A-Z]\w*)+\.)*[A-Z]\w*$"
        sw = Stopwatch.StartNew()
        result = Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase)
        sw.Stop()
        Console.WriteLine("{0} in {1}", result, sw.Elapsed)

        Dim aheadPattern As String = "^((?=[A-Z])\w+\.)*[A-Z]\w*$"
        sw = Stopwatch.StartNew()
        result = Regex.IsMatch(input, aheadPattern, RegexOptions.IgnoreCase)
        sw.Stop()
        Console.WriteLine("{0} in {1}", result, sw.Elapsed)
    End Sub
End Module
' The example displays the following output:
'       False in 00:00:03.8003793
'       False in 00:00:00.0000866

İlk normal ifade deseni, ^(([A-Z]\w*)+\.)*[A-Z]\w*$aşağıdaki tabloda gösterildiği gibi tanımlanır.

Desen Açıklama
^ Eşleştirmeyi dizenin başlangıcında başlatın.
([A-Z]\w*)+\. Ardından bir nokta gelen sıfır veya daha fazla sözcük karakterinin bir veya birden çok kez ardından geldiği bir alfabetik karakterle (A-Z) eşleştirin. Yöntemi seçeneğiyle RegexOptions.IgnoreCase çağrıldığından Regex.IsMatch bu karşılaştırma büyük/küçük harfe duyarlı değildir.
(([A-Z]\w*)+\.)* Önceki desenle sıfır veya daha çok kez eşleştirin.
[A-Z]\w* Ardından sıfır veya daha fazla karakter gelen alfabetik bir karakterle eşleştirin.
$ Giriş dizesinin sonunda eşleşmeyi bitir.

İkinci normal ifade deseni olan ^((?=[A-Z])\w+\.)*[A-Z]\w*$, pozitif bir lookahead onayı kullanır. Aşağıdaki tabloda gösterildiği gibi tanımlanmıştır.

Desen Açıklama
^ Eşleştirmeyi dizenin başlangıcında başlatın.
(?=[A-Z]) İleriye yönelik olarak birinci karaktere bakın ve alfabetik (A-Z) ise eşleştirmeyi devam ettirin. Yöntemi seçeneğiyle RegexOptions.IgnoreCase çağrıldığından Regex.IsMatch bu karşılaştırma büyük/küçük harfe duyarlı değildir.
\w+\. Ardından bir nokta gelen bir veya daha fazla sözcük karakterini eşleştirin.
((?=[A-Z])\w+\.)* Ardından sıfır veya daha çok kez bir nokta gelen bir veya birden çok sözcük karakteri desenini eşleştirin. İlk sözcük karakterinin alfabetik olması gerekir.
[A-Z]\w* Ardından sıfır veya daha fazla karakter gelen alfabetik bir karakterle eşleştirin.
$ Giriş dizesinin sonunda eşleşmeyi bitir.

Genel performans konuları

Aşağıdaki öneriler özellikle aşırı geri izlemeyi önlemek için değildir, ancak normal ifadenizin performansını artırmaya yardımcı olabilir:

  1. Yoğun olarak kullanılan desenleri önceden derle. Bunu yapmak için en iyi yol, önceden derlemek için normal ifade kaynak oluşturucuyu kullanmaktır. Kaynak oluşturucu uygulamanız için kullanılamıyorsa, örneğin .NET 7 veya üstünü hedeflemiyorsanız veya derleme zamanında deseni bilmiyorsanız seçeneğini kullanın RegexOptions.Compiled .

  2. Yoğun kullanılan Regex nesnelerini önbelleğe alın. Bu, kaynak oluşturucuyu kullanırken örtük olarak oluşur. Aksi takdirde, statik Regex yöntemlerini kullanmak veya bir Regex nesnesi oluşturup atmak yerine bir Regex nesnesi oluşturun ve yeniden kullanmak üzere depolayın.

  3. Bir uzaklıktan eşleştirmeye başlayın. Eşleşmelerin her zaman desene belirli bir uzaklığı aşacak şekilde başlayacağını biliyorsanız, gibi Regex.Match(String, Int32)bir aşırı yükleme kullanarak uzaklığı geçirin. Bu, altyapının dikkate alması gereken metin miktarını azaltır.

  4. Yalnızca ihtiyacınız olan bilgileri toplayın. Yalnızca eşleşmenin oluşup oluşmadığını bilmeniz gerekiyorsa, öğesini tercih edin Regex.IsMatch. Bir şeyin kaç kez eşleşip eşleşmediğini bilmeniz gerekiyorsa kullanmayı tercih edin Regex.Count. Yalnızca bir eşleşmenin sınırlarını bilmeniz gerekiyorsa ancak bir eşleşmenin yakalamalarıyla ilgili hiçbir şey bilmiyorsanız, kullanmayı Regex.EnumerateMatchestercih edin. Altyapının sağlaması gereken bilgiler ne kadar az olursa o kadar iyidir.

  5. Gereksiz yakalamalardan kaçının. Deseninizdeki parantezler varsayılan olarak bir yakalama grubu oluşturur. Yakalamalara ihtiyacınız yoksa, bunun yerine yakalama olmayan grupları belirtin RegexOptions.ExplicitCapture veya kullanın. Bu, altyapının bu yakalamaları izlemesini sağlar.

Ayrıca bkz.