33

I'm new to C# and while exploring the language features, I came across something strange:

struct Foo
{
    public Foo Identity() { return this; }

    public static void Bar(Foo? foo)
    {
        Foo foo1 = foo?.Identity().Value; // Does not compile
        Foo foo2 = (foo?.Identity()).Value; // Compiles
    }
}

Could anyone explain to me why the parenthesis are needed?

Lukas92
  • 363
  • 2
  • 11
  • 1
    In the first case you are trying to access a member named `Value` in `Foo`, which does not exist. In the second statement, `Value` refers to a property of `Nullable`. – xfx Jul 05 '16 at 14:18
  • 9
    If you really think about it, calling `.Value` on an expression that includes the null conditional operator is contradictory (you either expect a null or you don't). You would most likely want to use a null coalescing operator instead, in which case the parenthesis are not needed. e.g.: `Foo foo2 = foo?.Identity() ?? `; – sstan Jul 05 '16 at 14:28

2 Answers2

40

Could anyone explain to me why the parenthesis are needed?

Because Identity() returns a Foo (not a Foo?) and thus has no Value property. If foo is null, the null will propagate through the Identity call.

When you put parentheses around it, the results of the expression is a Nullable<Foo> which does have a Value property.

Also note that if foo is null, then you will be calling Value on a Nullable<Foo> that has no value, and will get an exception at run-time. Some static analyzers will recognize that you have a possible null-reference exception waiting to happen and warn you.

If you expand them to their equivalents without null-propagation it will be more clear:

Foo foo1;
if(foo != null)
{
    foo1 = foo.Identity().Value;  // not possible - Foo has no Value property.
}
else
{
    foo1 = null;  // also not possible 
}

Foo foo2;
Foo? temp;
if(foo != null)
{
    temp = foo.Identity();
}
else
{
   temp = null;  // actually a Nullable<Foo> with no value
}
foo2 = temp.Value;  // legal, but will throw an exception at run-time if foo is null

If Identity() returns Foo, why does Foo foo3 = foo?.Identity(); not compile ?

The equivalent of that would be:

Foo foo3
if(foo != null)
{
    foo3 = foo.Identity();
}
else
{
    foo3 = null;  // not possible 
}
D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • The joys of using the same symbol to mean different operators. – BoltClock Jul 05 '16 at 14:15
  • You won against my very slow computer and network :) – Camilo Terevinto Jul 05 '16 at 14:15
  • If `Identity()` returns `Foo`, why does `Foo foo3 = foo?.Identity();` not compile ? – Lukas92 Jul 05 '16 at 14:32
  • Because that's how null-propagation works. See my edit to see the equivalent without null-propagation. – D Stanley Jul 05 '16 at 14:39
  • I don't get it. In your last example "The equivalent of that would be:" why is `foo3 = null;` not possible? Also, [here, see "Return a nullable type (System.Nullable) if the target member returns a value type"](https://msdn.microsoft.com/en-us/magazine/dn802602.aspx), in the case `foo.Identity().Value` the target member is `Identity()`, which returns a value type, so why isn't that promoted to a nullable type? – davidbak Jul 05 '16 at 19:59
  • 1
    @davidbak 1) Because `Foo` is a _struct_ and cannot be null. 2) Because the null propagation evaluates _the entire expression_ as if the value were not null. The reason for the parentheses is to limit the propagation to just that part of the expression. – D Stanley Jul 05 '16 at 20:05
0

I think it was a good decision from the c# team to do it this way. Consider the below scenario:

If the struct was :

struct Foo
{
    public int ID { set; get; }

    public Foo Identity() { return this; }

    public static void Bar(Foo? foo)
    {
        int? foo1 = foo?.Identity().ID; // compile
        Foo foo2 = (foo?.Identity()).Value; // Compiles
    }
}

If you didn't need the parenthesis to access the Nullable result, you wouldn't be able to access the ID property. Since the below will not compile:

int? foo2 = (foo?.Identity()).GetValueOrDefault()?.ID

When you write foo?.Identity(). what is after the . is of type Foo returned by Identity(). However in (foo?.Identity()). what is after the . is Foo? which is the actual result of the whole statement foo?.Identity().

ErikE
  • 48,881
  • 23
  • 151
  • 196
Zein Makki
  • 29,485
  • 6
  • 52
  • 63