29

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.

Patrick Hofman
  • 143,714
  • 19
  • 222
  • 294
Sinatr
  • 18,856
  • 9
  • 75
  • 248
  • 2
    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` 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)` – kjbartel Sep 15 '15 at 08:02
  • @kjbartel: I think it's due to `?.` returning `null` if the expression was `null`, and not `default(T)`. – Joey Sep 15 '15 at 08:06
  • 1
    Func is nullable. – kjbartel Sep 15 '15 at 08:09
  • why there is no error for `Action` 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. @kjbartel – M.kazem Akhgary Sep 15 '15 at 08:15
  • 2
    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?`. – user4003407 Sep 15 '15 at 08:26
  • @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`. – kjbartel Sep 15 '15 at 08:31
  • 3
    @kjbartel 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. – user4003407 Sep 15 '15 at 08:37

3 Answers3

40

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.

Patrick Hofman
  • 143,714
  • 19
  • 222
  • 294
  • Adding `T:class` clause introduces another problem, shall I ask another question or is it an easy one? I have `public class FeatureBool: Feature { }` 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'"*. Not sure if there is a better choice than `class` (I can't use `object`) or should I do something with `FeatureBool` itself? – Sinatr Sep 15 '15 at 08:09
  • 4
    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? – Patrick Hofman Sep 15 '15 at 08:11
  • 1
    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` but not `Nullable`. thats why there is no error for `Action.Invoke`. – M.kazem Akhgary Sep 15 '15 at 08:28
  • 2
    @Sinatr you can do it in another way, refer T to struct : `public abstract class Feature where T : struct`. then mark all `T` types as null. `public Func` ,`public Action`,`public T? Value`.however in this way you can only work with value types. – M.kazem Akhgary Sep 15 '15 at 08:34
  • @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. – kjbartel Sep 15 '15 at 08:49
  • 1
    @kjbartel this answer is correct. _Since not everything can be null,_ thats true and thats why `nullable` is not valid. because only non-nullable things can be marked as `nullable`. – M.kazem Akhgary Sep 15 '15 at 08:57
  • my mistake. `void` is considered nullable type (there is no nullable) . the `.?` operator leaves the return type as `void` . – M.kazem Akhgary Sep 15 '15 at 09:10
  • 2
    @kjbartel `Invoke()` yields `T`. If `GetValue` is `null`, the `?` operator sets the return value of type `T` to `null`, which it can't. – Patrick Hofman Sep 15 '15 at 09:35
  • 1
    ahhh....... @PatrickHofman you're right. It looks at the type of the return value of the last method in the chain. – kjbartel Sep 15 '15 at 09:38
  • "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? – kjbartel Sep 16 '15 at 01:28
  • 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() ?? 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?. – bj0 May 10 '16 at 22:02
9

T must be a reference type or a nullable type

public abstract class Feature<T> where T : class
{
    // ...
}
Patrick Hofman
  • 143,714
  • 19
  • 222
  • 294
György Kőszeg
  • 13,565
  • 3
  • 27
  • 53
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.

Joey
  • 316,376
  • 76
  • 642
  • 652
  • @PanagiotisKanavos: `Nullable` is treated by the compiler as nullable, like it should, but [it's a value type](http://referencesource.microsoft.com/#mscorlib/system/nullable.cs,ffebe438fd9cbf0e). – Joey Sep 15 '15 at 08:05
  • should have checked the reference source first ! – Panagiotis Kanavos Sep 15 '15 at 08:06