Skip to content

Commit 99bd19f

Browse files
committed
Using ReadOnlySpan<Char> instead of Char[]. Adding reimpl for .NET 4.x.
Using ReadOnlySpan<Char> instead of Char[]. Adding reimpl for .NET 4.x.
1 parent 46bcffe commit 99bd19f

3 files changed

Lines changed: 143 additions & 9 deletions

File tree

src/Hashids.net/ArrayExtensions.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Buffers;
33
using System.Collections.Generic;
44
using System.Text.RegularExpressions;
@@ -66,5 +66,18 @@ public static IEnumerable<Match> GetMatches(this MatchCollection matches) // Nee
6666
}
6767
#endif
6868
}
69+
70+
#if NETCOREAPP3_1_OR_GREATER
71+
/// <remarks>This method exists because <see cref="ReadOnlySpan{T}"/> does not implement <see cref="IEnumerable{T}"/>.</remarks>
72+
public static bool Any<T>(this ReadOnlySpan<T> span, Func<T,bool> predicate)
73+
{
74+
for(int i = 0; i < span.Length; i++)
75+
{
76+
if(predicate(span[i])) return true;
77+
}
78+
79+
return false;
80+
}
81+
#endif
6982
}
7083
}

src/Hashids.net/Hashids.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
22
using System;
33
using System.Linq;
44
using System.Text.RegularExpressions;
@@ -73,7 +73,7 @@ public Hashids(
7373
}
7474

7575
/// <remarks>This method uses <c>out</c> params instead of returning a ValueTuple so it works with .NET 4.6.1.</remarks>
76-
private static void InitCharArrays(string alphabet, string seps, char[] salt, out char[] alphabetChars, out char[] sepChars, out char[] guardChars)
76+
private static void InitCharArrays(string alphabet, string seps, ReadOnlySpan<char> salt, out char[] alphabetChars, out char[] sepChars, out char[] guardChars)
7777
{
7878
alphabetChars = alphabet.ToCharArray().Distinct().ToArray();
7979
sepChars = seps.ToCharArray();
@@ -235,9 +235,9 @@ public virtual string DecodeHex(string hash)
235235
return result;
236236
}
237237

238-
private string GenerateHashFrom(long[] numbers)
238+
private string GenerateHashFrom(ReadOnlySpan<long> numbers)
239239
{
240-
if (numbers == null || numbers.Length == 0 || numbers.Any(n => n < 0))
240+
if (numbers.Length == 0 || numbers.Any(n => n < 0))
241241
return string.Empty;
242242

243243
long numbersHashInt = 0;
@@ -331,7 +331,7 @@ private string GenerateHashFrom(long[] numbers)
331331
return result;
332332
}
333333

334-
private int BuildReversedHash(long input, char[] alphabet, char[] hashBuffer)
334+
private int BuildReversedHash(long input, ReadOnlySpan<char> alphabet, char[] hashBuffer)
335335
{
336336
var length = 0;
337337
do
@@ -346,13 +346,13 @@ private int BuildReversedHash(long input, char[] alphabet, char[] hashBuffer)
346346
return length;
347347
}
348348

349-
private long Unhash(string input, char[] alphabet)
349+
private long Unhash(string input, ReadOnlySpan<char> alphabet)
350350
{
351351
long number = 0;
352352

353353
for (var i = 0; i < input.Length; i++)
354354
{
355-
var pos = Array.IndexOf(alphabet, input[i]);
355+
var pos = alphabet.IndexOf(input[i]);
356356
number = (number * _alphabetLength) + pos;
357357
}
358358

@@ -429,7 +429,7 @@ private char[] CreatePooledBuffer(int alphabetLength, char lottery)
429429
return buffer;
430430
}
431431

432-
private static void ConsistentShuffle(char[] alphabet, int alphabetLength, char[] salt, int saltLength)
432+
private static void ConsistentShuffle(char[] alphabet, int alphabetLength, ReadOnlySpan<char> salt, int saltLength)
433433
{
434434
if (salt.Length == 0)
435435
return;

src/Hashids.net/ReadOnlySpan.cs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
5+
namespace HashidsNet
6+
{
7+
#if !NETCOREAPP3_1_OR_GREATER
8+
internal struct ReadOnlySpan<T> : IReadOnlyList<T>
9+
{
10+
public static implicit operator ReadOnlySpan<T>(T[] array)
11+
{
12+
return new ReadOnlySpan<T>(array);
13+
}
14+
15+
public static implicit operator ReadOnlySpan<T>(ArraySegment<T> segment)
16+
{
17+
return new ReadOnlySpan<T>(segment.Array, startIndex: segment.Offset, count: segment.Count);
18+
}
19+
20+
public static implicit operator ArraySegment<T>(ReadOnlySpan<T> self)
21+
{
22+
return self.AsArraySegment();
23+
}
24+
25+
private readonly T[] array;
26+
private readonly int startIndex;
27+
private readonly int count;
28+
29+
public ReadOnlySpan(T[] array)
30+
: this(array, startIndex: 0, count: array?.Length ?? 0)
31+
{
32+
}
33+
34+
public ReadOnlySpan(T[] array, int startIndex, int count)
35+
{
36+
this.array = array ?? Array.Empty<T>();
37+
this.startIndex = startIndex;
38+
this.count = count;
39+
40+
if (this.array.Length == 0)
41+
{
42+
if (startIndex is not (0 or -1) ) throw new ArgumentOutOfRangeException(paramName: nameof(startIndex), actualValue: startIndex, message: "Value must be zero or -1 for empty arrays.");
43+
if (count != 0 ) throw new ArgumentOutOfRangeException(paramName: nameof(count) , actualValue: count , message: "Value must be zero for empty arrays.");
44+
}
45+
else
46+
{
47+
if (count < 0 ) throw new ArgumentOutOfRangeException(paramName: nameof(count) , actualValue: count , message: "Value must be non-negative.");
48+
if (startIndex < 0 ) throw new ArgumentOutOfRangeException(paramName: nameof(startIndex), actualValue: startIndex, message: "Value must be non-negative.");
49+
50+
if (startIndex >= array.Length) throw new ArgumentOutOfRangeException(paramName: nameof(startIndex), actualValue: startIndex, message: "Value exceeds the length of the provided array.");
51+
if (startIndex + count > array.Length) throw new ArgumentOutOfRangeException(paramName: nameof(count) , actualValue: count , message: "Value (plus " + nameof(startIndex) + ") exceeds the length of the provided array.");
52+
}
53+
}
54+
55+
public T this[int index]
56+
{
57+
get
58+
{
59+
if(index < 0 || index >= this.count)
60+
{
61+
throw new ArgumentOutOfRangeException(paramName: nameof(index), actualValue: index, message: "Value must be between 0 and " + nameof(this.Length) + " (exclusive).");
62+
}
63+
else
64+
{
65+
int sourceIndex = this.startIndex + index;
66+
return this.array[sourceIndex];
67+
}
68+
}
69+
}
70+
71+
public int Length => this.count;
72+
public int Count => this.count; // Specifying both Length and Count for source-level compatibility with both IReadOnlyList<T>.Count and Array<T>.Length (and of course, ReadOnlySpan<T>.Length)
73+
74+
private int EndIndex => ( this.startIndex + this.count ) - 1;
75+
76+
public int IndexOf(T value)
77+
{
78+
int sourceIndex = Array.IndexOf(this.array, value, startIndex: this.startIndex, count: this.count);
79+
if (sourceIndex < 0) return -1;
80+
return this.startIndex + sourceIndex;
81+
}
82+
83+
public IEnumerator<T> GetEnumerator()
84+
{
85+
if( this.count == 0 )
86+
{
87+
return ((IEnumerable<T>)Array.Empty<T>()).GetEnumerator();
88+
}
89+
else
90+
{
91+
return this.AsEnumerable().GetEnumerator();
92+
}
93+
}
94+
95+
private IEnumerable<T> AsEnumerable()
96+
{
97+
int endIdx = this.EndIndex;
98+
for (int i = this.startIndex; i <= endIdx; i++)
99+
{
100+
yield return this.array[i];
101+
}
102+
}
103+
104+
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
105+
106+
public ArraySegment<T> AsArraySegment() => new ArraySegment<T>(this.array, offset: this.startIndex, count: this.count);
107+
108+
/// <remarks>This method exists because using Linq's extensions over <see cref="IEnumerable{T}"/> or <see cref="IReadOnlyList{T}"/> are a lot slower than doing it directly.</remarks>
109+
public bool Any(Func<T,bool> predicate)
110+
{
111+
int endIdx = this.EndIndex;
112+
for (int i = this.startIndex; i <= endIdx; i++)
113+
{
114+
if(predicate(this.array[i])) return true;
115+
}
116+
117+
return false;
118+
}
119+
}
120+
#endif
121+
}

0 commit comments

Comments
 (0)