Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apiCount.include.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
| `netcoreapp3.1` | 880 |
| `net5.0` | 764 |
| `net6.0` | 660 |
| `net7.0` | 519 |
| `net8.0` | 400 |
| `net7.0` | 520 |
| `net8.0` | 401 |
| `net9.0` | 253 |
| `net10.0` | 186 |
| `net11.0` | 56 |
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ The package targets `netstandard2.0` and is designed to support the following ru
| `netcoreapp3.1` | 880 |
| `net5.0` | 764 |
| `net6.0` | 660 |
| `net7.0` | 519 |
| `net8.0` | 400 |
| `net7.0` | 520 |
| `net8.0` | 401 |
| `net9.0` | 253 |
| `net10.0` | 186 |
| `net11.0` | 56 |
Expand Down
48 changes: 40 additions & 8 deletions src/Polyfill/Regex/RegexCache.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET7_0_OR_GREATER && FeatureMemory
#if !NET9_0_OR_GREATER
#nullable enable
namespace System.Text.RegularExpressions;

Expand All @@ -10,15 +10,16 @@ namespace System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Globalization;
using System.Threading;

// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.Cache.cs
/// <summary>Cache used to store Regex instances used by the static methods on Regex.</summary>
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
#if PolyUseEmbeddedAttribute
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
#endif
// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.Cache.cs
/// <summary>Cache used to store Regex instances used by the static methods on Regex.</summary>
internal sealed class RegexCache
{
const int DefaultMaxCacheSize = 15;
Expand Down Expand Up @@ -67,14 +68,26 @@ public static int MaxCacheSize
}
}

public static Regex GetOrAdd(string pattern)
// Keys a regex constructed without an explicit timeout, which takes the default match timeout. Not a valid
// match timeout, so it never collides with an explicit one.
static readonly TimeSpan s_defaultTimeoutKey = TimeSpan.MinValue;

public static Regex GetOrAdd(string pattern) =>
GetOrAdd(pattern, RegexOptions.None);

public static Regex GetOrAdd(string pattern, RegexOptions options)
{
var key = new Key(pattern, RegexOptions.None, Regex.InfiniteMatchTimeout);
if (pattern is null)
{
throw new ArgumentNullException(nameof(pattern));
}

var key = new Key(pattern, GetCultureKey(options), options, s_defaultTimeoutKey);

var regex = Get(key);
if (regex is null)
{
regex = new Regex(pattern);
regex = new Regex(pattern, options);
Add(key, regex);
}

Expand All @@ -83,7 +96,12 @@ public static Regex GetOrAdd(string pattern)

public static Regex GetOrAdd(string pattern, RegexOptions options, TimeSpan matchTimeout)
{
Key key = new Key(pattern, options, matchTimeout);
if (pattern is null)
{
throw new ArgumentNullException(nameof(pattern));
}

Key key = new Key(pattern, GetCultureKey(options), options, matchTimeout);

Regex? regex = Get(key);
if (regex is null)
Expand All @@ -95,6 +113,17 @@ public static Regex GetOrAdd(string pattern, RegexOptions options, TimeSpan matc
return regex;
}

// A regex captures the casing rules of the culture it was constructed under, so the culture is part of the key.
static string GetCultureKey(RegexOptions options)
{
if ((options & RegexOptions.CultureInvariant) != 0)
{
return "";
}

return CultureInfo.CurrentCulture.ToString();
}

static Regex? Get(Key key)
{
long lastAccessedStamp = 0;
Expand Down Expand Up @@ -176,12 +205,14 @@ static void Add(Key key, Regex regex)
internal readonly struct Key : IEquatable<Key>
{
readonly string _pattern;
readonly string _culture;
readonly RegexOptions _options;
readonly TimeSpan _matchTimeout;

public Key(string pattern, RegexOptions options, TimeSpan matchTimeout)
public Key(string pattern, string culture, RegexOptions options, TimeSpan matchTimeout)
{
_pattern = pattern;
_culture = culture;
_options = options;
_matchTimeout = matchTimeout;
}
Expand All @@ -191,6 +222,7 @@ public override bool Equals([NotNullWhen(true)] object? obj) =>

public bool Equals(Key other) =>
_pattern.Equals(other._pattern) &&
_culture.Equals(other._culture) &&
_options == other._options &&
_matchTimeout == other._matchTimeout;

Expand Down
18 changes: 9 additions & 9 deletions src/Polyfill/Regex/RegexPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@ static partial class Polyfill
/// </summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.count?view=net-11.0#system-text-regularexpressions-regex-count(system-string-system-string)
public static int Count(string input, string pattern) =>
new Regex(pattern).Count(input);
RegexCache.GetOrAdd(pattern).Count(input);

/// <summary>
/// Searches an input string for all occurrences of a regular expression and returns the number of matches.
/// </summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.count?view=net-11.0#system-text-regularexpressions-regex-count(system-string-system-string-system-text-regularexpressions-regexoptions)
public static int Count(string input, string pattern, RegexOptions options) =>
new Regex(pattern, options).Count(input);
RegexCache.GetOrAdd(pattern, options).Count(input);

/// <summary>
/// Searches an input string for all occurrences of a regular expression and returns the number of matches.
/// </summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.count?view=net-11.0#system-text-regularexpressions-regex-count(system-string-system-string-system-text-regularexpressions-regexoptions-system-timespan)
public static int Count(string input, string pattern, RegexOptions options, TimeSpan matchTimeout) =>
new Regex(pattern, options, matchTimeout).Count(input);
RegexCache.GetOrAdd(pattern, options, matchTimeout).Count(input);

#endif

Expand All @@ -49,21 +49,21 @@ public static int Count(string input, string pattern, RegexOptions options, Time
/// </summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.count?view=net-11.0#system-text-regularexpressions-regex-count(system-readonlyspan((system-char))-system-string)
public static int Count(ReadOnlySpan<char> input, string pattern) =>
new Regex(pattern).Count(input);
RegexCache.GetOrAdd(pattern).Count(input);

/// <summary>
/// Searches an input span for all occurrences of a regular expression and returns the number of matches.
/// </summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.count?view=net-11.0#system-text-regularexpressions-regex-count(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions)
public static int Count(ReadOnlySpan<char> input, string pattern, RegexOptions options) =>
new Regex(pattern, options).Count(input);
RegexCache.GetOrAdd(pattern, options).Count(input);

/// <summary>
/// Searches an input span for all occurrences of a regular expression and returns the number of matches.
/// </summary>
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.count?view=net-11.0#system-text-regularexpressions-regex-count(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan)
public static int Count(ReadOnlySpan<char> input, string pattern, RegexOptions options, TimeSpan matchTimeout) =>
new Regex(pattern, options, matchTimeout).Count(input);
RegexCache.GetOrAdd(pattern, options, matchTimeout).Count(input);

/// <summary>
/// Indicates whether the specified regular expression finds a match in the specified input span, using the specified matching options and time-out interval.
Expand Down Expand Up @@ -114,21 +114,21 @@ public static ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<char> input, st
//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratesplits?view=net-11.0#system-text-regularexpressions-regex-enumeratesplits(system-readonlyspan((system-char))-system-string)
public static ValueSplitEnumerator EnumerateSplits(ReadOnlySpan<char> input, string pattern)
{
var regex = new Regex(pattern);
var regex = RegexCache.GetOrAdd(pattern);
return new ValueSplitEnumerator(input, regex, 0);
}

//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratesplits?view=net-11.0#system-text-regularexpressions-regex-enumeratesplits(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions)
public static ValueSplitEnumerator EnumerateSplits(ReadOnlySpan<char> input, string pattern, RegexOptions options)
{
var regex = new Regex(pattern, options);
var regex = RegexCache.GetOrAdd(pattern, options);
return new ValueSplitEnumerator(input, regex, 0);
}

//Link: https://learn.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex.enumeratesplits?view=net-11.0#system-text-regularexpressions-regex-enumeratesplits(system-readonlyspan((system-char))-system-string-system-text-regularexpressions-regexoptions-system-timespan)
public static ValueSplitEnumerator EnumerateSplits(ReadOnlySpan<char> input, string pattern, RegexOptions options, TimeSpan matchTimeout)
{
var regex = new Regex(pattern, options, matchTimeout);
var regex = RegexCache.GetOrAdd(pattern, options, matchTimeout);
return new ValueSplitEnumerator(input, regex, 0);
}

Expand Down
37 changes: 29 additions & 8 deletions src/Split/net461/Regex/RegexCache.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
// <auto-generated />
#pragma warning disable
#if FeatureMemory
#nullable enable
namespace System.Text.RegularExpressions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
/// <summary>Cache used to store Regex instances used by the static methods on Regex.</summary>
[ExcludeFromCodeCoverage]
[DebuggerNonUserCode]
#if PolyUseEmbeddedAttribute
[global::Microsoft.CodeAnalysis.EmbeddedAttribute]
#endif
/// <summary>Cache used to store Regex instances used by the static methods on Regex.</summary>
internal sealed class RegexCache
{
const int DefaultMaxCacheSize = 15;
Expand Down Expand Up @@ -58,20 +58,31 @@ public static int MaxCacheSize
}
}
}
public static Regex GetOrAdd(string pattern)
static readonly TimeSpan s_defaultTimeoutKey = TimeSpan.MinValue;
public static Regex GetOrAdd(string pattern) =>
GetOrAdd(pattern, RegexOptions.None);
public static Regex GetOrAdd(string pattern, RegexOptions options)
{
var key = new Key(pattern, RegexOptions.None, Regex.InfiniteMatchTimeout);
if (pattern is null)
{
throw new ArgumentNullException(nameof(pattern));
}
var key = new Key(pattern, GetCultureKey(options), options, s_defaultTimeoutKey);
var regex = Get(key);
if (regex is null)
{
regex = new Regex(pattern);
regex = new Regex(pattern, options);
Add(key, regex);
}
return regex;
}
public static Regex GetOrAdd(string pattern, RegexOptions options, TimeSpan matchTimeout)
{
Key key = new Key(pattern, options, matchTimeout);
if (pattern is null)
{
throw new ArgumentNullException(nameof(pattern));
}
Key key = new Key(pattern, GetCultureKey(options), options, matchTimeout);
Regex? regex = Get(key);
if (regex is null)
{
Expand All @@ -80,6 +91,14 @@ public static Regex GetOrAdd(string pattern, RegexOptions options, TimeSpan matc
}
return regex;
}
static string GetCultureKey(RegexOptions options)
{
if ((options & RegexOptions.CultureInvariant) != 0)
{
return "";
}
return CultureInfo.CurrentCulture.ToString();
}
static Regex? Get(Key key)
{
long lastAccessedStamp = 0;
Expand Down Expand Up @@ -147,18 +166,21 @@ static void Add(Key key, Regex regex)
internal readonly struct Key : IEquatable<Key>
{
readonly string _pattern;
readonly string _culture;
readonly RegexOptions _options;
readonly TimeSpan _matchTimeout;
public Key(string pattern, RegexOptions options, TimeSpan matchTimeout)
public Key(string pattern, string culture, RegexOptions options, TimeSpan matchTimeout)
{
_pattern = pattern;
_culture = culture;
_options = options;
_matchTimeout = matchTimeout;
}
public override bool Equals([NotNullWhen(true)] object? obj) =>
obj is Key other && Equals(other);
public bool Equals(Key other) =>
_pattern.Equals(other._pattern) &&
_culture.Equals(other._culture) &&
_options == other._options &&
_matchTimeout == other._matchTimeout;
public override int GetHashCode() =>
Expand All @@ -175,4 +197,3 @@ sealed class Node(Key key, Regex regex)
public long LastAccessStamp;
}
}
#endif
18 changes: 9 additions & 9 deletions src/Split/net461/Regex/RegexPolyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,33 @@ static partial class Polyfill
/// Searches an input string for all occurrences of a regular expression and returns the number of matches.
/// </summary>
public static int Count(string input, string pattern) =>
new Regex(pattern).Count(input);
RegexCache.GetOrAdd(pattern).Count(input);
/// <summary>
/// Searches an input string for all occurrences of a regular expression and returns the number of matches.
/// </summary>
public static int Count(string input, string pattern, RegexOptions options) =>
new Regex(pattern, options).Count(input);
RegexCache.GetOrAdd(pattern, options).Count(input);
/// <summary>
/// Searches an input string for all occurrences of a regular expression and returns the number of matches.
/// </summary>
public static int Count(string input, string pattern, RegexOptions options, TimeSpan matchTimeout) =>
new Regex(pattern, options, matchTimeout).Count(input);
RegexCache.GetOrAdd(pattern, options, matchTimeout).Count(input);
#if FeatureMemory
/// <summary>
/// Searches an input span for all occurrences of a regular expression and returns the number of matches.
/// </summary>
public static int Count(ReadOnlySpan<char> input, string pattern) =>
new Regex(pattern).Count(input);
RegexCache.GetOrAdd(pattern).Count(input);
/// <summary>
/// Searches an input span for all occurrences of a regular expression and returns the number of matches.
/// </summary>
public static int Count(ReadOnlySpan<char> input, string pattern, RegexOptions options) =>
new Regex(pattern, options).Count(input);
RegexCache.GetOrAdd(pattern, options).Count(input);
/// <summary>
/// Searches an input span for all occurrences of a regular expression and returns the number of matches.
/// </summary>
public static int Count(ReadOnlySpan<char> input, string pattern, RegexOptions options, TimeSpan matchTimeout) =>
new Regex(pattern, options, matchTimeout).Count(input);
RegexCache.GetOrAdd(pattern, options, matchTimeout).Count(input);
/// <summary>
/// Indicates whether the specified regular expression finds a match in the specified input span, using the specified matching options and time-out interval.
/// </summary>
Expand Down Expand Up @@ -77,17 +77,17 @@ public static ValueMatchEnumerator EnumerateMatches(ReadOnlySpan<char> input, st
#if FeatureMemory && FeatureValueTuple
public static ValueSplitEnumerator EnumerateSplits(ReadOnlySpan<char> input, string pattern)
{
var regex = new Regex(pattern);
var regex = RegexCache.GetOrAdd(pattern);
return new ValueSplitEnumerator(input, regex, 0);
}
public static ValueSplitEnumerator EnumerateSplits(ReadOnlySpan<char> input, string pattern, RegexOptions options)
{
var regex = new Regex(pattern, options);
var regex = RegexCache.GetOrAdd(pattern, options);
return new ValueSplitEnumerator(input, regex, 0);
}
public static ValueSplitEnumerator EnumerateSplits(ReadOnlySpan<char> input, string pattern, RegexOptions options, TimeSpan matchTimeout)
{
var regex = new Regex(pattern, options, matchTimeout);
var regex = RegexCache.GetOrAdd(pattern, options, matchTimeout);
return new ValueSplitEnumerator(input, regex, 0);
}
#endif
Expand Down
Loading
Loading