Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there something similar to NotNullIfNotNullAttribute for IEnumerable return values?

(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) };
}
like image 974
Heinzi Avatar asked Sep 14 '25 18:09

Heinzi


1 Answers

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.

like image 139
Rikki Gibson Avatar answered Sep 17 '25 08:09

Rikki Gibson