Selecting and filtering from a list with full nullable safety & warnings as errors
I have the type
Btn
in my sample below which simulates the portion of WinForms that I'm interested in.
I have a list of Btn
objects and my goal is to mutate it into a subset of Btn
objects that have their Tag
matching the Wanted
type without being opaque to the static analyser that detects potential null references (and avoiding the null-forgiving operator)
I ran the code below over on https://sharplab.io and it does not complain that OfType
could return any nullables.
Nonetheless, the code fails because the middle element (which will be a tuple of null
s) still gets returned.
Is this an issue with the REPL or is the static analyser really not seeing that the tuple types could end up being null? Perhaps there is a cleaner way to achieve what I want. Do note that I cannot alter the type held by Btn.Tag
, it's an object in WinForms and I have no control over the code that decided to expose the types via that field.
32 Replies
I'd recommend filtering it out with
.Where()
firstAs an addendum, I am aware that I can chain
.Where(tup => tup.Item1 is not null)
but that seems silly when OfType
is supposed to remove nullablesProbably has to do with how reference types are nullable by default. So all elements that your
Select()
returns are of type (Btn?, Wanted?)
null
is not really a type
You could not do .OfType<null>()
for exampleI have set the pragma
#nullable enable
it would emit a warning
It has occurred to me that I can make it work correctly by explicitly stating the tuple as nullable - like so:
This functions correctly, but it does strike me as a bit verboseA
.Where()
would be simpler.Where()
is not visible to the analyser, it won't know that the tuple's types can't be null
or more specifically, .Where()
will filter out the nullables, but it won't transform the type from T?, T2?
to T, T2
True
Unfortulately, nullability for reference types in C# is a bit of a "landlord special"
Just about kinda works, but... not that well
well, I have a working solution now, as posted above, but it'd be cosmetically pleasant if I could do it more tersely than explicitly writing out the tuple as a nullable type to make it cooperate
this seems like an issue with tuples specifically
This makes it cleaner:
the right hand side of the ternary is no longer ambiguous so I don't have to specify the generics
if anyone thinks they can make it shorter, I'm all ears.
yeah the problem is that
foo is (C, D)
does a tuple pattern check. it checks the items individually. but OfType
will do is ValueTuple<C, D>
. and that can't ever be true, because generic parameters in structs don't have varianceif that is the case, I'd expect it to still fail nullable analysis
how do you mean?
if the return type of
Select
is an IEnumerable<(T?, T2?)>
then it should fail when OfType
is supposed to be returning an IEnumerable<(T, T2)>
in my original code sample, the ternary was returning either (btn, w)
or (null, null)
which is going to be an IEnumerable<(T?, T2?)>
well no, because like angius mentioned, nullable reference types are kinda just... sugar for analyzers
(T1?, T2?)
is still ValueTuple<T1, T2>
as I have read and understood the documentation, the analyser needs to be able to prove that a type can't be null
this is somehow escaping the analyser
it's not really surprising. the linq query goes through so many layers, it's impossible to retain that information
all
OfType
does is check value is ReturnType
and that check is true for ValueTuple<T1, T2>
, ValueTuple<T1?, T2>
, ValueTuple<T1, T2?>
, and ValueTuple<T1?, T2?>
ero
REPL Result: Success
Console Output
Compile: 504.577ms | Execution: 32.420ms | React with ❌ to remove this embed.
ero
REPL Result: Success
Console Output
Compile: 487.519ms | Execution: 52.578ms | React with ❌ to remove this embed.
everywhere I've had nullable errors, it's been because it cannot prove it's non-nullable and I've had to change the code so that passes through.
if you're saying information is lost through LINQ, that should defacto result in the analyser deciding that it can't prove the types are non-null and emitting an error
ero
REPL Result: Success
Console Output
Compile: 488.115ms | Execution: 48.817ms | React with ❌ to remove this embed.
sure, raise an issue in dotnet/roslyn
i'm sure people in that repo can explain it better than i ever could anyway
but what you're seeing is a general issue with generic parameters that don't have variance
like in classes or structs
many things go into this
maybe https://github.com/dotnet/roslyn/discussions is best
I suspect this isn't unique to
ValueTuple
- I imagine this is applicable to all struct
types and it seems this is by design:
https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references#structsNullable reference types - C#
This article provides an overview of nullable reference types. Learn how the feature provides safety against null reference exceptions, for new and existing projects.
that makes structs a bit of a turnoff for me if they essentially have inferior nullability safety
Arrays and structs that contain reference types are known pitfalls in nullable references and the static analysis that determines null safety. In both situations, a non-nullable reference might be initialized to null, without generating warnings.:cough:
this specifically isn't about structs.
it's about the generic arguments
ero
REPL Result: Success
Console Output
Compile: 490.639ms | Execution: 28.665ms | React with ❌ to remove this embed.
this is the class
Tuple<T1, T2>
I see, if I'm understanding this right, whether it's allowed to work or not is dependent on whether or not you define constraints:
https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references#generics
Nullable reference types - C#
This article provides an overview of nullable reference types. Learn how the feature provides safety against null reference exceptions, for new and existing projects.
meaning, if I understand this correctly, that
ValueTuple
/Tuple
have no such constraintslst.Where(btn => btn.Tag is Wanted).Select(btn => (btn, (Wanted)btn.Tag))
, maybe?
i think you might need nullable suppression in the Select
though, still...
i've always kinda wanted a Where
+ Select
combination for this, but i'm not sure how you would design it