[Proposal] Support ! (not) on bool-returning delegates and method groups #6284
Replies: 11 comments 3 replies
-
This is more complex than it appears. The following two statements are not equivalent: var auth = new Auth();
var allowed1 = users.Where(auth.IsAllowed);
var allowed2 = users.Where(x => auth.IsAllowed(x)); The first one creates a delegate which stores the current value of auth = null;
var array1 = allowed1.ToArray(); // Works fine
var array2 = allowed2.ToArray(); // NullReferenceException There have been various attempts by various people to treat I suppose it would be possible to implement I'm pretty sure I've seen a dup for this, but I can't find it... |
Beta Was this translation helpful? Give feedback.
-
You can make the compiler generate a static method for var predicate = (Func<User, bool>)Delegate.CreateDelegate(typeof(Func<User, bool>), auth, typeof(Program).GetMethod(nameof(AutoMethod)));
users.Where(predicate);
public static bool AutoMethod(Auth auth, User user)
{
return !auth.IsAllowed(user);
} It is not equivalent to the variable-capturing lambda, but it behaves like without "Lifting" method groups this way for any combination of operators seems like a pretty cool idea as well: users.Where(auth.GetUserLevel > 0); A problem arises when local variables are used in the expression though, since that does require an allocation. users.Where(auth.GetUserLevel > inputLevel); It would be interesting if this kind of code could be used to create a lambda with a closure but without transforming locals to fields of the closure, i.e. capturing only their immediate values. |
Beta Was this translation helpful? Give feedback.
-
We have considered this and very much so not like it. The lack of any syntax to distinguish a closure versus a non closure is basically a nonstarter |
Beta Was this translation helpful? Give feedback.
-
You know you can make your own public static IEnumerable<T> WhereNot<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
if (source is null)
throw new ArgumentNullException(nameof(source));
if (predicate is null)
throw new ArgumentNullException(nameof(predicate));
return WhereNotIterator(source, predicate);
IEnumerable<T> WhereNotIterator(IEnumerable<T> source, Func<T, bool> predicate)
{
foreach (var item in source)
{
if (!predicate(item))
yield return item;
}
}
} |
Beta Was this translation helpful? Give feedback.
-
@JustNrik You don't really need all this, you can just return |
Beta Was this translation helpful? Give feedback.
-
@CyrusNajmabadi Is it a closure in a normal sense? As mentioned by @IllidanS4 method group can produce
|
Beta Was this translation helpful? Give feedback.
-
Lazy evaluation has some hidden corners like in your example here (there are much more), but I will support @ashmind idea and will expand it. |
Beta Was this translation helpful? Give feedback.
-
These statements: var allowed1 = users.Where(auth.IsAllowed);
var allowed2 = users.Where(x => auth.IsAllowed(x)); are not equivalent even now and they are both valid. But there is a trap: if you found a bug because the statement So let me ignore non-equivalence as a valid counterargument and let me go even further. Allow a sequence of bool returning functions accepting the same or convertible argument list to be grouped by all logical operators and automatically generate combined bool function for this expression. So this statement: bool mayExpectRainbow = MayBeRaining(forecast) && MayBeSunny(forecast); could be written as: bool mayExpectRainbow = (MayBeRaining && MayBeSunny)(forecast); And in similar Linq example user would choose how to write the statement, both forms will be valid and will be not equivalent the same way: bool daysWeMayExpectRainbow1 = forecasts.Where(MayBeRaining && MayBeSunny).Select(f => f.Day);
bool daysWeMayExpectRainbow2 = forecasts.Where(f => MayBeRaining(f) && MayBeSunny(f)).Select(f => f.Day);
bool daysWeMayExpectRainbow3 = forecasts.Where(f => (MayBeRaining && MayBeSunny)(f)).Select(f => f.Day); And support not only && operator but other operators as well, e.g. unary not (as in original proposition), but now with chaining: var notEmptyStrings = strings.Where(!string.IsNullOrEmpty);
var numericStrings = strings.Where(!string.IsNullOrEmpty && CanBeParsedAsInt);
bool daysWeMayNotExpectRainbow = forecasts.Where(MayBeRaining ^ MayBeSunny).Select(day); // weird one, incorrect one, but XOR is also logical operator :) |
Beta Was this translation helpful? Give feedback.
-
I made a test trying to achieve same effect with simple generic functions Not, And, etc. However, when using them compiler requires generic type arguments to be specified and instead of: Not(string.IsNullOrEmpty) I had to use this syntax: Not<string?>(string.IsNullOrEmpty) |
Beta Was this translation helpful? Give feedback.
-
Problem
Consider these two situations:
While logic is symmetrical, the delegate usage is not.
Proposal
Allow operator
!
to apply to bool-returning delegates and method groups. For simplicity, let's say only the ones that don't haveref
orout
parameters are allowed.Result of applying
!
to delegate of bool-returning typeF
is another delegate of typeF
, which passes all of its parameters to the original, then negates the original result.When
!
is applied to a method group, the method group is first processed as if!
wasn't present. After that it is similar to 2.Concerns
Should we consider other operators? I wouldn't mind something like
IsAllowed && !IsSuperUser
to automatically generateu => IsAllowed(u) && !IsSuperUser(u)
, but it might be very hard to contextually resolve for method groups.It feels significantly less common, so I am not including it in this proposal.
Beta Was this translation helpful? Give feedback.
All reactions