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>? arrtoImmutableArray<string> arrorvar 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.
