Zev Spitz
Zev Spitz

Reputation: 15317

Nullability mismatch in generic type parameter between two method arguments

I've written the following extension method:

// using System.Collections.Generic;

internal static class TExtensions {
    internal static bool In<T>(this T val, HashSet<T> hs) => hs.Contains(val);
}

and am trying to consume it as follows:

var s = DateTime.Now.Hour < 15 ? "abcd" : null;
var hs = new HashSet<string>();
Console.WriteLine(s.In(hs));

The compiler gives me a warning on the last line:

CS8620 Argument of type 'HashSet' cannot be used for parameter 'hs' of type 'HashSet' in 'bool TExtensions.In(string? val, HashSet? hs)' due to differences in the nullability of reference types.

because the compiler is resolving the T type parameter as the type of s, or string?; but the hashset is not a HashSet<T> which would be a hashset of nullable string (HashSet<string?>), rather it is a hashset of non-nullable string (HashSet<string>).

I could resolve this by either wrapping in a null check:

if (s is { }) {
    var result = s.In(hs);
}

or explicitly typing the hashset as having nullable elements:

var hs = new HashSet<string?>();

but is there some way to use the nullable attributes to allow this scenario? Or is there something else I could change in the In extension method?

Upvotes: 1

Views: 1701

Answers (1)

pescolino
pescolino

Reputation: 3123

You can use the [AllowNull] attribute for the first parameter of the In method:

internal static bool In<T>([AllowNull] this T val, HashSet<T> hs) => hs.Contains(val);

However as you described in your question the actual problem here is that the inferred generic type parameter is determined by s. The In method will be called as In<string?> which requires the hashset to be HashSet<string?>. You must tell the compiler to use string instead of string?: s.In<string>(hs).

If you can constraint to non-nullable reference types you can use this:

internal static bool In<T>(this T? val, HashSet<T> hs) where T : class => val != null && hs.Contains(val);

For value types the implementation would be:

internal static bool In<T>(this T? val, HashSet<T> hs) where T : struct => val.HasValue && hs.Contains(val.Value);

Note that only the first version (with the [AllowNull] attribute) works if the type parameter itself is nullable.

Instead of changing the extension method you can of course just use the null-forgiving-operator to shut up the compiler: s.In(hs!) or s!.In(hs).

Upvotes: 1

Related Questions