-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Add WhereNotNull to help with nullability tracking #30381
Comments
If you write: Where(x => x != null) the compiler is already going to cache the delegate automatically. |
It might be non-trivial, but there are a myriad number of places this same kind of issue can show up, and I don't think we should address each as a one-off solved by adding additional methods for each such case. My preference is to wait and see what the actual broad impact is (we don't even have LINQ annotated yet), and then ideally come up with a holistic solution around such cases. If it turns out that this specific case is literally the only one that matters, then it could be relevant, but we should not rush it. |
The compiler will cache one delegate per type parameter, per use site. There are likely to be hundreds of identical such delegates across a large code base - a quick search just for "x => x != null" turned up 97 in ours. It's a small benefit, but perhaps not irrelevant.
Agreed. I was just bringing this up here as a point of discussion. Hopefully the Roslyn and CoreFx teams can work something out on this together. Thanks for your input! |
This is something Roslyn could have an optimization pass to consolidate if deemed impactful. Developers shouldn't be required to manually do such folding by hand. |
Maybe WhereNotDefault |
Linq is now annotated and the issue remains. dotnet/roslyn#41363 has some good comments: |
why isn't |
@john-h-k +1 on the |
I want to add public static IEnumerable<V> SelectNotNull<T,V>(this IEnumerable<T> items,Func<T,V> func) where V : class
{
foreach(var item in items)
{
var value = func(item);
if(value != null)
yield return value;
}
}
public static IEnumerable<V> SelectNotNull<T,V>(this IEnumerable<T> items,Func<T,V?> func) where V : struct
{
foreach(var item in items)
{
var value = func(item);
if(value.HasValue)
yield return value.Value;
}
} It could also be used as condition filtering. If the value was not satisfied condition we could return null to skip it |
As a Linq user, I keep running into this issue fairly often. And even if the compiler is eventually smart enough to infer this, I think the proposed API still adds value by making a fairly common Linq filter built-in. I've marked the API as ready for review so we can discuss the necessary next steps. |
I don't know why we'd promote this one Where + predicate to a top-level method. Why not WhereNotEmptyString? WhereContainsString? WhereNotEmptyCollection? And so on. If it's purely about nullable annotations, one character ( |
In my uses cases, I found it usually not solvable by adding a single Let's say I write this: var nonNullCustomers = collection.Where(c => c is not null); The compiler infers var nonNullCustomers = (IEnumerable<Customer>)collection.Where(c => c is not null); or give up on type inference IEnumerable<Customer> nonNullCustomers = collection.Where(c => c is not null)!; or explicitly do something in the Linq query to change the null state: var result = customers.Where(c is not null).Select(c => c!); All solutions seem substantially less desirable than just writing var result = customers.WhereNotNull();
There is a line somewhere; where that exactly lies is a bit in the eye of the beholder. We have certain conveniences, such as |
Also want to add that
Because But I could support |
I agree with @stephentoub about the overload why this one and not some other. As he stated, let's say you have a collection of string and do Besides, adding specific overload won't help building better analyzers. I would be in favor of having some mechanism to "promote" what is learned from within the |
I don't think compiler will smart enough to propagate the information of nullability from arbitrary delegate to I don't understand why someone think of |
What are you then doing with nonNullCustomers? If you're returning it from something that needs
You still get type inference. You're just telling the compiler you know it's all not null.
Can you gather some data? We should be able to analyze all usage of Where in large codebases (sync with @marklio) and demonstrate that this is, say, the 20% case of using Where, and I'll think about it differently (from a convenience perspective; from a nullability analysis perspective, even with that I think solving this in the compiler is better). Without that, sure, I of course believe people do it, but I don't believe it's so common that it's worth special-casing it.
Why not? The compiler can see the lambda. It already does the flow analysis to determine whether the value returned from the lambda is maybe-null or non-null. The feature there is about enabling that information to propagate out, which has little to do with smarts and more to do with just making it work. And when it does work, it'll handle well more than just
Because every decision about whether to filter something in or out is a Boolean decision. We already have such overloads for making such Boolean decisions. With, you know, Booleans. |
How can compiler know the internal mechanism of Or you meant that you want compiler to treat That's what I want to call that compiler is not smart enough and it really should not as much smart as that. It would have unintended behaviour
There was |
The compiler already knows about Where and Select and other LINQ methods. The C# specification actually defines the behavior of what these methods do as it binds to them as part of query comprehension syntax.
No, it exists to filter out things that aren't of the right type, and in particular to help with going from non-generic to generic collections. It, along with Cast, are the two primary ways for bridging the non-generic world to LINQ.
The proposed |
This meant you try to make every other C# functions in the system as second class citizen and promote the
that aren't of the right type in essense that also include
I'm understand the latter argument, but still Especially for converting nullable to value it save so much more than just |
This issue is about adding one API that itself "promotes the Where" to be special by adding a dedicated method that handles only a single case. The compiler already knows about these methods and can handle them for nullability much more generally.
But isn't limited to null. It's like saying StringBuilder.Append is the same thing because it handles null strings specially.
Because it's insanely common, used by practically every codebase that uses LINQ, there's no other LINQ method it can be implemented in terms of, and enables fluid syntax as the rest of LINQ provides. It also already exists. We're talking about investing in new API here, for something that's already doable with minimal ceremony; it needs to be worth it.
I asked @terrajobst for data earlier. |
How is that promoting And so you mean items.Where((item) => item is MyType).Cast<MyType>();
// items.OfType<MyType>(); To be honest I think of this feature is similar to
It's opposite. I think of it as "limited to only a type it want to filter" while
This is also subjective. I admittedly also use it. But for me it just only save relatively a few character, without it we can also var list = new List<MyType>();
list.AddRange(/* some linq we normally append .ToList() at the end */); Like this |
It's taking something you can easily write today: source.Where(s => s != null); and creating a dedicated method for it: source.WhereNotNull(); That's promoting this very specific use of Where to its own method. That's the whole purpose of this issue.
No, OfType is primarily about bringing non-generic enumerables into the generic world, which is why it accepts
I don't think it's subjective at all. |
Depends on the scenario, but it's often subsequent Linq operations. Here is an example: var nonNullCustomers = collection.Where(c => c is not null)
.OrderBy(c => c.FirstName) // Warning
.ThenBy(c => c.LastName); // Warning
foreach (var c in nonNullCustomers)
{
Console.WriteLine(c.FirstName + " " + c.LastName); // Warning
} I would have to put a var nonNullCustomers = collection.Where(c => c is not null)
.OrderBy(c => c!.FirstName)
.ThenBy(c => c!.LastName);
foreach (var c in nonNullCustomers)
{
Console.WriteLine(c!.FirstName + " " + c.LastName);
} In cases like this it makes the most sense to deal with the var nonNullCustomers = collection.Where(c => c is not null)
.Select(c => c!);
.OrderBy(c => c.FirstName)
.ThenBy(c => c.LastName);
foreach (var c in nonNullCustomers)
{
Console.WriteLine(c.FirstName + " " + c.LastName);
} Which is why I'd prefer to write this: var nonNullCustomers = collection.WhereNotNull()
.OrderBy(c => c.FirstName)
.ThenBy(c => c.LastName);
foreach (var c in nonNullCustomers)
{
Console.WriteLine(c!.FirstName + " " + c.LastName);
} |
If it would be called as promote anything I would call it promoting Which is perfectly fine, because
With your argument this meant promoting the very specific use of And also we have both version of |
As a C# dev, I'm following this discussion not because I'd like to add Now, if there is a better solution to make That said, I don't understand enough the frameworks internal to see how this could be possible. Would it be some attribute magic, like Is dotnet/csharplang#8383 the correct place to track the "nullability detection issue" ? |
Or dotnet/csharplang#3951 I assume.
My guess is it will end up being specific to the LINQ members C# already knows about. |
Given that it would require people to change their code, and the current analysis says it's only about 3% of libraries where it's even possibly related; and that the main use should ideally be solved by the compiler... there's no need for it in the BCL. |
Given this class class MyClass
{
public int? SomeProp { get; set; }
} And some collection MyClass[] ar = ...; It's easier and less error prone to get values with int[] props = ar.Select(c => c.SomeProp).WhereNotNull().ToArray() than with int[] props = ar.Select(c => c.SomeProp).Where(x => x.HasValue).Select(x => x!.Value).ToArray(); |
See dotnet/csharplang#8383
Currently the C# compiler can't track how the lambda in a
Where
method effects nullability.For example
Warns with:
This could definitely be solved by improved compiler tooling, but doing so would be non-trivial.
A workaround would be to introduce a
WhereNotNull
method into CoreFX, so that consumers won't need to use the suppression operator themselves.A further advantage would be that using
WhereNotNull
instead ofWhere
would avoid allocating a lambda for an extremely common Linq method. This could be done either by caching the lambda as aFunc<object, bool>
, which avoids the need to update the rest of Linq to be aware of a new Linq method, or by creating a custom enumerator forWhereNotNull
which avoids the overhead of a virtual function call, but requires updating the rest of Linq.API Proposal
API Usage
I've deliberately not constrained the first API (i.e. omitted
where T: class
) so that unconstrained generic code can use this API as well. Of course, this won't work well with nullable value types, but that's a general problem of unconstrained generic code and nullability in general, so this isn't a new problem.Risks
None.
The text was updated successfully, but these errors were encountered: