In the following code, I get error CS1061 about a not found method or extension method "EmptyIfNull".
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
ImmutableDictionary<string, string>? dic = new[]
{ KeyValuePair.Create("Hello", "World") }.ToImmutableDictionary();
ImmutableArray<string>? arr = new[]
{ "Hello", "World" }.ToImmutableArray();
Console.WriteLine(string.Join(' ', dic.EmptyIfNull()));
Console.WriteLine(string.Join(' ', arr.EmptyIfNull()));
static class EnumerableExtensions
{
public static IEnumerable<TSource> EmptyIfNull<TSource>(
this IEnumerable<TSource>? source)
{
return source ?? Enumerable.Empty<TSource>();
}
}
error CS1061: 'ImmutableArray?' does not contain a definition for 'EmptyIfNull' and no accessible extension method 'EmptyIfNull' accepting a first argument of type 'ImmutableArray?' could be found (are you missing a using directive or an assembly reference?)
I know about two ways to fix the code to produce the expected output:
- Change
ImmutableArray<string>? arr
toImmutableArray<string> arr
orvar arr
. That defeats the purpose of the EmptyIfNull() extension, though. - Change
arr.EmptyIfNull()
toarr.EmptyIfNull<string>()
.
I believe the behavior for immutable arrays is different from the one for immutable dictionaries due to the fact that immutable arrays are structs while immutable dictionaries are classes. I just do not understand why type inference fails for the structs, and when the extension gets the type parameter explicitly, it the code works. By the way, I tested that when I assign null to the dic and arr variables and use the latter fix to the code, it actually works as expected, producing an empty enumerable.
I have been warned that the immutable array is boxed when used as an enumerable, which comes with a performance penalty. But I find it a non-issue here because the EmptyIfNull() extension is meant to be used to fix inputs to LINQ queries where a collection happens to be null instead of empty. The LINQ query will have a much higher overhead.
I am using .NET 6 and C# 10 if it changes anything. But the issue seems to be the same on SharpLab.io, which runs a more modern custom build of .NET SDK and its latest C# version. I believe such specifics should be mostly irrelevant to this question.
CodePudding user response:
I think what's going on here is...
Let's simplify it:
int? x = 3;
x.Test();
static class EnumerableExtensions
{
public static void Test<T>(this IEquatable<T>? source)
{
}
}
int
is a struct. Therefore int?
is syntactic sugar for Nullable<int>
. While int
may implement IEquatable<int>
, Nullable<int>
does not. However, if you box the int?
, it turns into a boxed int
, which does implement IEquatable<int>
. Confused yet?
If you ask the compiler to find an overload of Test
which accepts an int?
, I think the type inference is failing because the only overload accepts IEquatable<int>
, and int?
doesn't implement IEquatable<int>
. However when you specify T
as int
directly this bypasses type inference, and the compiler works out that it needs to box x
, which gives it a boxed int
, which does implement IEquatable<int>
, and everything is OK.
The detail is probably in this section of the spec, if you like reading such things.