C# Func – Null-Conditional Operator Doesn’t Work with Func Inside a Generic Method

c++null-coalescingnull-conditional-operator

Is this a compiler bug or is there a specific chosen reason why the null-conditional operator doesn't work with Func inside of generic methods?

To give an example the following doesn't compile

public static T Test<T>(Func<T> func)
{
    return func?.Invoke() ?? default(T);
}

The error the compiler produces is CS0023 Operator '?' cannot be applied to operand of type 'T'

I'm aware that you can achieve the same doing this however:

public static T Test<T>(Func<T> func)
{
    return func != null ? func() : default(T);
}

So why is it that it's not allowed?

To elaborate further Action<T> however works as expected.

public static void Test<T>(Action<T> action, T arg)
{
    action?.Invoke(arg);
}

Update (2017-01-17):

After some more research, it makes even less sense, even with the following:

Let's say we have a class (Reference-type)

public class Foo
{
    public int Bar { get; set; }
}

and let's say we have a Func<int>

Func<int> fun = () => 10;

The following works:

// This work
var nullableBar = foo?.Bar; // type of nullableBar is int?
var bar = nullableBar ?? default(int); // type of bar is int

// And this work
nullableBar = fun?.Invoke(); // ditto
bar = nullableBar ?? default(int); // ditto

Which means according to the logic applied there then a Func<T> of a value-type using null-conditional and null-coalescing operators should work.

However as soon the left-hand generic type of the null-conditional is generic with no constraints then it can't apply the same logic that it should be able to considering it can apply the same logic to both value-types and reference-types when the types are explicitly applied.

I'm aware of the compilers constraints, it just doesn't make sense to me why it doesn't allow it and why it wants the output to be different whether it's a reference or value type considering manually applying the types will yield expected results.

Best Answer

Unfortunately I believe you have hit a edge case of the compiler. The ?. operator needs to return default(RetrunTypeOfRHS) for classes and default(Nullable<RetrunTypeOfRHS>) for structs. Because you have not constrained T to be classes or structs it can't tell which one to promote to.

The reason Action<T> works is because the return type of the right hand side is void for both cases so it does not need to decide which promotion to do.

You will need to use the long form you showed or have two methods with different constraints on T

    public static T TestStruct<T>(Func<T> func) where T : struct
    {
        return func?.Invoke() ?? default(T);
    }

    public static T TestClass<T>(Func<T> func) where T : class
    {
        return func?.Invoke(); // ?? default(T); -- This part is unnecessary, ?. already
                                                 // returns default(T) for classes.
    }
Related Question