(This is a question about C# nullable reference types and generics.)
I know that I can use NotNullIfNotNullAttribute
to specify that the return value of my method may be null if, and only if, an input is null:
[return: NotNullIfNotNull(nameof(defaultValue))]
public T? GetValueOrDefault<T>(T? defaultValue)
{
return GetValueFromSomewhere<T>() ?? defaultValue;
}
Is there something similar for methods returning IEnumerable<T>
? I'd like to tell the compiler that all elements of the returned IEnumerable are non-null if the input parameter is non-null.
[return: ???]
public IEnumerable<T?> GetValuesOrDefault<T>(T? defaultValue)
{
return GetValuesFromSomewhere<T>().Select(x => x ?? defaultValue);
}
Here's a MCVE (fiddle) to play around with:
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.CodeAnalysis;
public class Program
{
public static void Main()
{
string? s1 = GetValueOrDefault<string>(null);
string s2 = GetValueOrDefault<string>(""); // works, C# realizes that the return value can't be null
IEnumerable<string?> e1 = GetValuesOrDefault<string>(null);
IEnumerable<string> e2 = GetValuesOrDefault<string>(""); // I want to get rid of the warning here
}
[return: NotNullIfNotNull(nameof(defaultValue))]
public static T? GetValueOrDefault<T>(T? defaultValue)
{
return GetValueFromSomewhere<T>() ?? defaultValue;
}
public static IEnumerable<T?> GetValuesOrDefault<T>(T? defaultValue)
{
return GetValuesFromSomewhere<T>().Select(x => x ?? defaultValue);
}
// Dummy placeholders for my MCVE. My real methods read values from my DB repository.
private static T? GetValueFromSomewhere<T>() => default(T);
private static IEnumerable<T?> GetValuesFromSomewhere<T>() => new T?[] { default(T) };
}
I think what you want is already possible.
In this case, both the input and output whose nullability we want to modify are of type T
, a type parameter on the enclosing method. We don't need [NotNullIfNotNull]
here — we can just use method type argument inference to achieve what we want. SharpLab.
// ok
IEnumerable<string> a = GetValuesOrDefault(new[] { "a", null }, defaultValue: "b");
IEnumerable<string> b = GetValuesOrDefault(new[] { "a", "b" }, defaultValue: "b");
// warning: Nullability of reference types in value of type 'IEnumerable<string?>' doesn't match target type 'IEnumerable<string>'.
IEnumerable<string> c = GetValuesOrDefault(new[] { "a", null }, defaultValue: null);
IEnumerable<T> GetValuesOrDefault<T>(IEnumerable<T?> valuesFromSomewhere, T defaultValue)
{
return valuesFromSomewhere.Select(x => x ?? defaultValue);
}
This gives us the point of control we want here: If the defaultValue
going in is non-nullable, the result IEnumerable has a non-nullable type argument. If the defaultValue
going in may be null, the result IEnumerable has a nullable type argument.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With