Skip to content

Commit ab0aea1

Browse files
author
Thomas Biel
committed
added a single value cache to improve performance for repeated calls to unmodified instances
1 parent 05de12c commit ab0aea1

File tree

5 files changed

+117
-15
lines changed

5 files changed

+117
-15
lines changed

VerbalExpressions/RegexCache.cs

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
using System.Text.RegularExpressions;
3+
4+
namespace CSharpVerbalExpressions
5+
{
6+
public sealed class RegexCache
7+
{
8+
private bool hasValue;
9+
private Key key;
10+
private Regex regex;
11+
12+
private class Key
13+
{
14+
public Key(string pattern, RegexOptions options)
15+
{
16+
this.Pattern = pattern;
17+
this.Options = options;
18+
}
19+
20+
public string Pattern { get; private set; }
21+
public RegexOptions Options { get; private set; }
22+
23+
public override bool Equals(object obj)
24+
{
25+
var key = obj as Key;
26+
return key != null &&
27+
key.Pattern == this.Pattern &&
28+
key.Options == this.Options;
29+
}
30+
31+
public override int GetHashCode()
32+
{
33+
return this.Pattern.GetHashCode() ^ this.Options.GetHashCode();
34+
}
35+
}
36+
37+
/// <summary>
38+
/// Gets the already cached value for a key, or calculates the value and stores it.
39+
/// </summary>
40+
/// <param name="pattern">The pattern used to create the regular expression.</param>
41+
/// <param name="options">The options for regex.</param>
42+
/// <returns>The calculated or cached value.</returns>
43+
public Regex Get(string pattern, RegexOptions options)
44+
{
45+
if (pattern == null) throw new ArgumentNullException("pattern");
46+
47+
lock (this)
48+
{
49+
var current = new Key(pattern, options);
50+
if (this.hasValue && current.Equals(this.key))
51+
{
52+
return this.regex;
53+
}
54+
55+
this.regex = new Regex(pattern, options);
56+
this.key = current;
57+
this.hasValue = true;
58+
return this.regex;
59+
}
60+
}
61+
}
62+
}

VerbalExpressions/VerbalExpressions.cs

+5-15
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@ public static VerbalExpressions DefaultExpression
3636

3737
#region Private Members
3838

39+
private readonly RegexCache regexCache = new RegexCache();
40+
3941
private string _prefixes = "";
4042
private string _source = "";
4143
private string _suffixes = "";
4244
private RegexOptions _modifiers = RegexOptions.Multiline;
43-
45+
4446
#endregion Private Members
4547

4648
#region Private Properties
@@ -52,23 +54,11 @@ private string RegexString
5254

5355
private Regex PatternRegex
5456
{
55-
get { return new Regex(this.RegexString, _modifiers); }
57+
get { return regexCache.Get(this.RegexString, _modifiers); }
5658
}
5759

5860
#endregion Private Properties
59-
60-
#region Constructors
61-
62-
public VerbalExpressions()
63-
{
64-
}
65-
66-
static VerbalExpressions()
67-
{
68-
}
69-
70-
#endregion Constructors
71-
61+
7262
#region Public Methods
7363

7464
#region Helpers

VerbalExpressions/VerbalExpressions.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
</ItemGroup>
7171
<ItemGroup>
7272
<Compile Include="CommonRegex.cs" />
73+
<Compile Include="RegexCache.cs" />
7374
<Compile Include="VerbalExpressions.cs" />
7475
<Compile Include="Properties\AssemblyInfo.cs" />
7576
</ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Text.RegularExpressions;
4+
using CSharpVerbalExpressions;
5+
using NUnit.Framework;
6+
7+
namespace VerbalExpressionsUnitTests
8+
{
9+
[TestFixture]
10+
public class PerformanceTests
11+
{
12+
13+
[Test]
14+
public void VerbalExpression_Is_Not_Slower_Than_Direct_Use_Of_Regex()
15+
{
16+
const string someUrl = "https://www.google.com";
17+
18+
var verbEx = VerbalExpressions.DefaultExpression
19+
.StartOfLine()
20+
.Then("http")
21+
.Maybe("s")
22+
.Then("://")
23+
.Maybe("www.")
24+
.AnythingBut(" ")
25+
.EndOfLine();
26+
27+
var regex = new Regex(@"^http(s)?://([\w-]+.)+[\w-]+(/[\w- ./?%&=])?$");
28+
29+
var timeVerbEx = MeasureCallDuration(() => verbEx.IsMatch(someUrl));
30+
var timeRegex = MeasureCallDuration(() => regex.IsMatch(someUrl));
31+
32+
Assert.That(timeVerbEx - timeRegex, Is.LessThan(TimeSpan.FromSeconds(0.1)));
33+
}
34+
35+
private static TimeSpan MeasureCallDuration(Action action)
36+
{
37+
var sw = Stopwatch.StartNew();
38+
39+
for (var i = 0; i < 10000; i++)
40+
{
41+
action();
42+
}
43+
44+
sw.Stop();
45+
return sw.Elapsed;
46+
}
47+
}
48+
}

VerbalExpressionsUnitTests/VerbalExpressionsUnitTests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<Compile Include="AddModifierTests.cs" />
4444
<Compile Include="AddTests.cs" />
4545
<Compile Include="CaptureTests.cs" />
46+
<Compile Include="PerformanceTests.cs" />
4647
<Compile Include="RepeatPreviousTest.cs" />
4748
<Compile Include="EndOfLineTests.cs" />
4849
<Compile Include="MaybeTests.cs" />

0 commit comments

Comments
 (0)