Search code examples
c#nullable

How to check whether the return type of a method is nullable or not in C# (Full nullable context enabled)


How can I check whether the return type of a method is nullable or not? The project setting <Nullable>enable</Nullable> is active.

Some examples (All methods are of type Task or Task<T>)

public sealed class Dto
{
    public int Test { get; set; }
}

public sealed class Dto3
{
    public int? Test { get; set; }
}

public async Task<Dto?> GetSomething()
{
    // This should be found as "Nullable enabled" as the `?` is set.
}

public async Task<Dto3> GetSomething2()
{
    // This should not be found as "Nullable enabled" as the `?` is missing.
}

public async Task<List<Dto>> GetSomething3()
{
    // This should not be found as "Nullable enabled" as the `?` is missing
    // (And the list must be initialized anyways thanks to nullable context).
}

public async Task<List<Dto?>> GetSomething3()
{
    // This should not be found as "Nullable enabled" as the `?` is missing in the first generic type after `Task`.
}

I already have code that iterates through the classes and methods in the search namespace like this:

var assembly = Assembly.GetAssembly(typeof(ISomethingService));

if (assembly is null)
{
    throw new InvalidOperationException("This should never happen");
}

var classes = assembly.GetTypes().Where(type => 
 (type.Namespace == typeof(ISomethingService).Namespace || type.Namespace == typeof(ISomethingService2).Namespace) && type.IsInterface);

foreach (var @class in classes)
{
    var methods = @class.GetMethods();

    foreach (var method in methods)
    {
        foreach (var genericType in method.ReturnType.GenericTypeArguments)
        {
            if (IsNullable(genericType))
            {
                Console.WriteLine($"Return type of method {@class.Name}.{method.Name} is nullable: {genericType.Name}");
            }
        }
    }
 }

The nullable check is currently implemented like this, but doesn't work as expected:

private static bool IsNullable<T>(T obj)
{
    if (obj == null) return true; // obvious
    Type type = typeof(T);
    if (!type.IsValueType) return true; // ref-type
    if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable<T>
    return false; // value-type
}

Is there a way to achive the desired result (E.g. only having IsNullable be true if there is a ? set on the first level (of generics) after the Task<T>?


Solution

  • In .NET 6+, you can use NullabilityInfoContext to find out whether a field, property, parameter, or event is nullable. See also this answer.

    In your case, you want to get the method's ReturnParameter, which is a PropertyInfo.

    bool ReturnNullableOrNullableTask(MethodInfo m) {
        var context = new NullabilityInfoContext();
        var returnParameter = m.ReturnParameter;
        var info = context.Create(returnParameter);
        if (info.ReadState == NullabilityState.Nullable) {
            return true; // the return type itself is null
        }
        // otherwise check if it is a Task<T>
        if (returnParameter.ParameterType.IsGenericType && returnParameter.ParameterType.GetGenericTypeDefinition() == typeof(Task<>)) {
            var firstTypeParameterInfo = info.GenericTypeArguments[0];
            if (firstTypeParameterInfo.ReadState == NullabilityState.Nullable) {
                return true;
            }
        }
        return false;
    }
    

    This works with both nullable value types and nullable reference types.