1

Background:

With C# 8 and suitable settings, you distinguish nullable reference types from reference types "guaranteed" to be nonnull.

Targeting .NET Core, there is some support for this in the .NET library, for example if I say:

var valueOfEnvVar = Environment.GetEnvironmentVariable("NoVarWithThisName");

then valueOfEnvVar will have type string?, not just string. This is because the library method GetEnvironmentVariable will return null when the requested environment variable does not exist. Some "secret" attributes in the DLL (apparently netcoreapp3.1\System.Runtime.Extensions.dll in this example) make this work under the hood.

As every reader of this knows, the advantage here is if I say e.g. valueOfEnvVar.Contains("test"), at compile-time I will get CS8602: Dereference of a possibly null reference. This prevents NullReferenceException hell.

So far so good.


Now for the question. Suppose I have:

static void M(IEnumerable<string> seq)
{
    var first = seq.FirstOrDefault(); // (1)
}

As you see, seq is a (nonnullable) IEnumerable<> of (nonnullable) strings. But there is a clear possibility it could be empty (if there was not, I would say .First(), surely).

Still, the C# compiler says first is nonnullable string. If I go on to say first.Contains("test"), the compiler will not stop or warn me. I am led directly into the run-time exception.

Why is this not as good as in the GetEnvironmentVariable example?

Of course, I try to help to compiler and say:

static void M(IEnumerable<string> seq)
{
    var first = (string?)seq.FirstOrDefault(); // (2)
}

or:

static void M(IEnumerable<string> seq)
{
    string? first = seq.FirstOrDefault(); // (3)
}

But it does not really help. The compiler still thinks it is safe to do first.Contains. No CS8602 for me.

In fact, Visual Studio development environment says my use of the type string? is dubious and comes with helpful "potential fixes" that lead me back to the form (1) which is wrong.

Why does this not work? (The thread Nullable reference types with generic return type seems to indicate that it should be technically possible to make this work even when FirstOrDefault has a return type TSource which at compile-time cannot be known to be a reference type.)

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • `Some "secret" attributes in the DLL (apparently netcoreapp3.1\System.Runtime.Extensions.dll in this example) make this work under the hood.` Does the other code, that doesn't do what you expect, have this secret attribute? – mjwills Apr 06 '20 at 01:49
  • @mjwills I do not think so, but it should have some attribute, should it not? Compare the linked thread. I cannot think of a more clear example of a return value that is sometimes null, than `FirstOrDefault`. So do you say I have found a bug in LINQ? – Jeppe Stig Nielsen Apr 06 '20 at 01:58
  • Does it change if you use ICollection instead? IEnumerable could have issues since it doesn't have certain guarantees. – Daniel Lorenz Apr 06 '20 at 02:05
  • Maybe a duplicate of?: https://stackoverflow.com/a/58671188/2729609 – Sebastian Schumann Apr 06 '20 at 05:03
  • 1
    [`FirstorDefault`](https://source.dot.net/#System.Linq/System/Linq/First.cs,8087366974af11d2) implementation shows that return type maybe null, there is no bug in `Linq`. It seems, that it isn't shipped yet in roslyn, according to GitHub issue [#38941](https://github.com/dotnet/roslyn/issues/38941) – Pavel Anikhouski Apr 06 '20 at 10:09
  • @PavelAnikhouski When was the `MaybeNullAttribute` inserted? It is not in my reference assembly which seems to have this name: "System.Linq, Version=4.2.2.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" and to be located in "C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\3.1.0\ref\netcoreapp3.1\System.Linq.dll". It is also not in my run-time assembly which has the same name and is located here: "C:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.1\System.Linq.dll" – Jeppe Stig Nielsen Apr 06 '20 at 16:51
  • It seems `System.Environment` was done in a pull request that is included, namely [coreclr/pull/23774](https://github.com/dotnet/coreclr/pull/23774), while `System.Linq.Enumerable` was done in a later pull request that we will have to wait until **.NET 5** to get, [corefx/pull/40651](https://github.com/dotnet/corefx/pull/40651). I will mark this question as a duplicate and write a comment in the other thread as well. – Jeppe Stig Nielsen Apr 11 '20 at 17:23

0 Answers0