Operator '?' cannot be applied to operand of type 'T'
Asked Answered
P

3

32

Trying to make Feature generic and then suddenly compiler said

Operator '?' cannot be applied to operand of type 'T'

Here is the code

public abstract class Feature<T>
{
    public T Value
    {
        get { return GetValue?.Invoke(); } // here is error
        set { SetValue?.Invoke(value); }
    }

    public Func<T> GetValue { get; set; }
    public Action<T> SetValue { get; set; }
}

It is possible to use this code instead

get
{
    if (GetValue != null)
        return GetValue();
    return default(T);
}

But I am wondering how to fix that nice C# 6.0 one-liner.

Piragua answered 15/9, 2015 at 7:49 Comment(7)
That's really interesting. I think it might be a bug. All of the answers which suggest using where T : class are missing the fact that you're checking if the Func<T> is null not a T, as your second block of code shows. If that works then the GetValue?.Invoke() syntax should work also. You should be able to write: return GetValue?.Invoke() ?? default(T)Sannyasi
@kjbartel: I think it's due to ?. returning null if the expression was null, and not default(T).Chronopher
Func<T> is nullable.Sannyasi
why there is no error for Action<T> while thats nullable too. the cause is something else. and i dont think its a bug. and my guess is because Action return type is void but Func returns T. @SannyasiHelix
I think, problem is that compiler can not determine result type of GetValue?.Invoke(). If T is class or Nullable<>, than result type should be T, but if T is struct, than result type should be T?.Remunerate
@PetSerAl That should be the same problem for any non-nullable type. For example an int such as from List?.Count which you can solve by using ?? such as List?.Count ?? 0.Sannyasi
@Sannyasi Result type of List?.Count is int?. It is know at compile time, that type should be promoted to nullable. Result type of GetValue?.Invoke() is T or T?. It is not know at compile time, should type be promoted to nullable, or it nullable already.Remunerate
N
45

Since not everything can be null, you have to narrow down T to be something nullable (aka an object). Structs can't be null, and neither can enums.

Adding a where on class does fix the issue:

public abstract class Feature<T> where T : class

So why doesn't it just work?

Invoke() yields T. If GetValue is null, the ? operator sets the return value of type T to null, which it can't. If T is int for example, it can't make it nullable (int?) since the actual type required (T = int) isn't.

If you change T to be int in your code, you will see the problem very clearly. The end result of what you ask is this:

get
{
    int? x = GetValue?.Invoke();
    return x.GetValueOrDefault(0);
}

This is not something the null-propagation operator will do for you. If you revert to the use of default(T) it does know exactly what to do and you avoid the 'problematic' null-propagation.

Norman answered 15/9, 2015 at 7:53 Comment(11)
Adding T:class clause introduces another problem, shall I ask another question or is it an easy one? I have public class FeatureBool: Feature<bool> { } and it says now "The type 'T' must be a reference type in order to use it as parameter 'T' in the generic type or method 'Feature<T>'". Not sure if there is a better choice than class (I can't use object) or should I do something with FeatureBool itself?Piragua
I would just accept that the C# compiler doesn't allow this and that you need your option #2 in order to make this work. Not everything has to be a one liner, right?Norman
The actuall error is Cannot lift conditional access expression type 'T' to nullable type. The .? operator wraps the return type of child into Nullable<>. The compiler can handle Nullable<void> but not Nullable<T>. thats why there is no error for Action<T>.Invoke.Helix
@Piragua you can do it in another way, refer T to struct : public abstract class Feature<T> where T : struct. then mark all T types as null. public Func<T?> ,public Action<T?>,public T? Value.however in this way you can only work with value types.Helix
@M.kazemAkhgary You should write that as an answer as this answer is not correct. Yes it solves the problem for classes but it doesn't work for value types which will work fine with the second getter version from the question.Sannyasi
@Sannyasi this answer is correct. Since not everything can be null, thats true and thats why nullable<T> is not valid. because only non-nullable things can be marked as nullable.Helix
my mistake. void is considered nullable type (there is no nullable<void>) . the .? operator leaves the return type as void .Helix
@Sannyasi Invoke() yields T. If GetValue is null, the ? operator sets the return value of type T to null, which it can't.Norman
ahhh....... @PatrickHofman you're right. It looks at the type of the return value of the last method in the chain.Sannyasi
"This is not something the null-propagation operator will do for you. If you revert to the use of default(T) it does know exactly what to do and you avoid the 'problematic' null-propagation." GetValue?.Invoke() ?? default(T) also doesn't work. What are you trying to say with this last sentence?Sannyasi
I just ran into this error, and it may not be a bug in the compiler but the error message is very mislead and makes it seem to be a bug. In my case I am applying the ?. operator to an object instance like so: obj?.Get<T>() ?? default(T). This is inside a generic function and it will compile with either where T: struct or where T: class, but not neither. This is pretty strange since it's the same code in both cases, I guess if you need to cover both cases you just have to create 2 identical overloads with different conditions?.Nootka
O
8

T must be a reference type or a nullable type

public abstract class Feature<T> where T : class
{
    // ...
}
Optime answered 15/9, 2015 at 7:53 Comment(0)
C
4

As far as I know the ?. operator is hardcoded to work with null, that is, it works for reference types or nullable value types, but not normal value types. The problem is likely that the operator returns null if the expression was null instead of default(T).

You might be able to fix it by restricting T to class here.

Chronopher answered 15/9, 2015 at 7:53 Comment(2)
@PanagiotisKanavos: Nullable<T> is treated by the compiler as nullable, like it should, but it's a value type.Chronopher
should have checked the reference source first !Chondriosome

© 2022 - 2024 — McMap. All rights reserved.