You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Using expression trees instead of simple lambda's in Arg.Is is less than ideal. For example, Arg.Is<object[]?>(x => x?.Length == 0) is not allowed because expression trees cannot contain null propagating operators. Next to that, the error NSubstitute gives contains the expression tree representation of lambda, instead of the code written. This makes parsing the error harder as the example above becomes x => ((x != null) AndAlso (ArrayLength(x) == 0)).
This issue can be fixed by using [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string predicateExpression = "". This attribute captures the exact expression inside the predicate parameter of Arg.Is:
publicstaticref T Is<T>(Predicate<T>predicate,[System.Runtime.CompilerServices.CallerArgumentExpression("predicate")]stringpredicateExpression=""){returnref ArgumentMatcher.Enqueue<T>(newPredicateArgumentMatcher<T>(predicate, predicateExpression));}
That PredicateArgumentMatcher is a clone of ExpressionArgumentMatcher and looks like:
NSubstitute.Exceptions.ReceivedCallsException : Expected to receive a call matching:
StoreLuggage(x => x?.Length == 0)
Actually received no matching calls.
Received 1 non-matching call (non-matching arguments indicated with '*' characters):
StoreLuggage(*<null>*)
Furthermore, calling methods is now possible from those Arg.Is expressions. It might not be the best idea to actually do, but at least all C# code is allowed, and it's no longer constraint by the expression tree limitations.
There a few challenges with this approach and that's why I've opened this issue (I might also have overlooked some details).
1: This approach requires Arg.Is to be wrapped in pragmas targetting NET6 and greater, because this feature is not available in older .NETs:
#if NET6_0_OR_GREATER
/// <summary>/// Match argument that satisfies <paramref name="predicate"/>./// If the <paramref name="predicate"/> throws an exception for an argument it will be treated as non-matching./// </summary>publicstaticref T Is<T>(Predicate<T>predicate,[System.Runtime.CompilerServices.CallerArgumentExpression("predicate")]stringpredicateExpression=""){returnref ArgumentMatcher.Enqueue<T>(newPredicateArgumentMatcher<T>(predicate, predicateExpression));}
#else/// <summary>/// Match argument that satisfies <paramref name="predicate"/>./// If the <paramref name="predicate"/> throws an exception for an argument it will be treated as non-matching./// </summary>publicstaticref T Is<T>(Expression<Predicate<T>>predicate){returnref ArgumentMatcher.Enqueue<T>(newExpressionArgumentMatcher<T>(predicate));}
#endif
2: predicateExpression is writable by the caller. SonarLinter, for example, gives a warning indicating that you should not do that, but it's not really problematic. It might also be considered a feature, so this depends on whether we like it.
3: When I was running my experiments the test CompatAndArgShouldBeInSync failed. I don't know if we can actually keep those in sync. Stuff to use in pre-C#7 compilers might not be amused by this change. So I rather discuss this before looking into that.
Let me know what you think! The change itself is rather small, but I can imagine that you might be hesitant to change something like this.
The text was updated successfully, but these errors were encountered:
Thanks @ThomasBleijendaal . I'm spiking out a way to support more flexible arg matching at the moment. I'll will try to ensure it meets this use case. I'll ping you when i'm done to take a look if that's ok?
Using expression trees instead of simple lambda's in
Arg.Is
is less than ideal. For example,Arg.Is<object[]?>(x => x?.Length == 0)
is not allowed because expression trees cannot contain null propagating operators. Next to that, the error NSubstitute gives contains the expression tree representation of lambda, instead of the code written. This makes parsing the error harder as the example above becomesx => ((x != null) AndAlso (ArrayLength(x) == 0))
.This issue can be fixed by using
[System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string predicateExpression = ""
. This attribute captures the exact expression inside thepredicate
parameter ofArg.Is
:That
PredicateArgumentMatcher
is a clone ofExpressionArgumentMatcher
and looks like:This results NSubstitute returning errors like:
Furthermore, calling methods is now possible from those
Arg.Is
expressions. It might not be the best idea to actually do, but at least all C# code is allowed, and it's no longer constraint by the expression tree limitations.There a few challenges with this approach and that's why I've opened this issue (I might also have overlooked some details).
1: This approach requires
Arg.Is
to be wrapped in pragmas targetting NET6 and greater, because this feature is not available in older .NETs:2:
predicateExpression
is writable by the caller. SonarLinter, for example, gives a warning indicating that you should not do that, but it's not really problematic. It might also be considered a feature, so this depends on whether we like it.3: When I was running my experiments the test
CompatAndArgShouldBeInSync
failed. I don't know if we can actually keep those in sync. Stuff to use in pre-C#7 compilers might not be amused by this change. So I rather discuss this before looking into that.Let me know what you think! The change itself is rather small, but I can imagine that you might be hesitant to change something like this.
The text was updated successfully, but these errors were encountered: