-
Notifications
You must be signed in to change notification settings - Fork 253
Improve search validator and in-memory evaluator. #443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
8223635
ea439e1
1d58969
517a6cd
15d9614
e8658cb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| using System.Collections; | ||
| using System.Diagnostics.CodeAnalysis; | ||
|
|
||
| namespace Ardalis.Specification; | ||
|
|
||
| internal abstract class Iterator<TSource> : IEnumerable<TSource>, IEnumerator<TSource> | ||
| { | ||
| private readonly int _threadId = Environment.CurrentManagedThreadId; | ||
|
|
||
| private protected int _state; | ||
| private protected TSource _current = default!; | ||
|
|
||
| public Iterator<TSource> GetEnumerator() | ||
| { | ||
| var enumerator = _state == 0 && _threadId == Environment.CurrentManagedThreadId ? this : Clone(); | ||
| enumerator._state = 1; | ||
| return enumerator; | ||
| } | ||
|
|
||
| public abstract Iterator<TSource> Clone(); | ||
| public abstract bool MoveNext(); | ||
|
|
||
| public TSource Current => _current; | ||
| object? IEnumerator.Current => Current; | ||
| IEnumerator<TSource> IEnumerable<TSource>.GetEnumerator() => GetEnumerator(); | ||
| IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | ||
|
|
||
| [ExcludeFromCodeCoverage] | ||
| void IEnumerator.Reset() => throw new NotSupportedException(); | ||
|
|
||
| public virtual void Dispose() | ||
| { | ||
| _current = default!; | ||
| _state = -1; | ||
| } | ||
| } |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| using System.Diagnostics; | ||
|
|
||
| namespace Ardalis.Specification; | ||
|
|
||
| public class SearchMemoryEvaluator : IInMemoryEvaluator | ||
| { | ||
| private SearchMemoryEvaluator() { } | ||
| public static SearchMemoryEvaluator Instance { get; } = new SearchMemoryEvaluator(); | ||
|
|
||
| public IEnumerable<T> Evaluate<T>(IEnumerable<T> query, ISpecification<T> specification) | ||
| { | ||
| if (specification.SearchCriterias is List<SearchExpressionInfo<T>> { Count: > 0 } list) | ||
| { | ||
| // The search expressions are already sorted by SearchGroup. | ||
| return new SpecLikeIterator<T>(query, list); | ||
| } | ||
|
|
||
| return query; | ||
| } | ||
|
|
||
| private sealed class SpecLikeIterator<TSource> : Iterator<TSource> | ||
| { | ||
| private readonly IEnumerable<TSource> _source; | ||
| private readonly List<SearchExpressionInfo<TSource>> _searchExpressions; | ||
|
|
||
| private IEnumerator<TSource>? _enumerator; | ||
|
|
||
| public SpecLikeIterator(IEnumerable<TSource> source, List<SearchExpressionInfo<TSource>> searchExpressions) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not switching to primary constructor syntax yet?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been hesitant because of the older TFMs. We need to check whether we need any polyfills or not (I assume not). |
||
| { | ||
| _source = source; | ||
| _searchExpressions = searchExpressions; | ||
| } | ||
|
|
||
| public override Iterator<TSource> Clone() | ||
| => new SpecLikeIterator<TSource>(_source, _searchExpressions); | ||
|
|
||
| public override void Dispose() | ||
| { | ||
| if (_enumerator is not null) | ||
| { | ||
| _enumerator.Dispose(); | ||
| _enumerator = null; | ||
| } | ||
| base.Dispose(); | ||
| } | ||
|
|
||
| public override bool MoveNext() | ||
| { | ||
| switch (_state) | ||
| { | ||
| case 1: | ||
| _enumerator = _source.GetEnumerator(); | ||
| _state = 2; | ||
| goto case 2; | ||
| case 2: | ||
| Debug.Assert(_enumerator is not null); | ||
| var searchExpressions = _searchExpressions; | ||
| while (_enumerator!.MoveNext()) | ||
| { | ||
| TSource sourceItem = _enumerator.Current; | ||
| if (IsValid(sourceItem, searchExpressions)) | ||
| { | ||
| _current = sourceItem; | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| Dispose(); | ||
| break; | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| // This would be simpler using Span<SearchExpressionInfo<TSource>> | ||
| // but CollectionsMarshal.AsSpan is not available in .NET Standard 2.0 | ||
| private static bool IsValid<T>(T sourceItem, List<SearchExpressionInfo<T>> list) | ||
| { | ||
| var groupStart = 0; | ||
| for (var i = 1; i <= list.Count; i++) | ||
| { | ||
| // If we reached the end of the list or the group has changed, we slice and process the group. | ||
| if (i == list.Count || list[i].SearchGroup != list[groupStart].SearchGroup) | ||
| { | ||
| if (IsValidInOrGroup(sourceItem, list, groupStart, i) is false) | ||
| { | ||
| return false; | ||
| } | ||
| groupStart = i; | ||
| } | ||
| } | ||
| return true; | ||
|
|
||
| static bool IsValidInOrGroup(T sourceItem, List<SearchExpressionInfo<T>> list, int from, int to) | ||
| { | ||
| var validOrGroup = false; | ||
| for (int i = from; i < to; i++) | ||
| { | ||
| if (list[i].SelectorFunc(sourceItem)?.Like(list[i].SearchTerm) ?? false) | ||
| { | ||
| validOrGroup = true; | ||
| break; | ||
| } | ||
| } | ||
| return validOrGroup; | ||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.