24

I thought T? is just a compiler shorthand for Nullable<T>. According to MSDN:

The syntax T? is shorthand for Nullable<T>, where T is a value type. The two forms are interchangeable.

However, there is a little (insignificant) difference: Visual Studio doesn't allow me to call static methods on shorthands:

bool b1 = Nullable<int>.Equals(1, 2); //no error
bool b2 = int?.Equals(1, 2); //syntax error "Invalid expression term 'int'"

Why? Is there any reason for this limitation?

vojta
  • 5,591
  • 2
  • 24
  • 64
  • 1
    It's a syntax thing. The parser can't figure out the `?.` – jwg May 31 '17 at 13:01
  • 2
    This is a good question, though I don't know what would be the meaning of that, What would we try to test with Nullable.Equals(1, 2)? (I'm not being mean, is a real question) – hardkoded May 31 '17 at 13:03
  • 2
    Starting with C# 6, `?.` used as null-conditional check, hence it is syntax error. The situation maybe different when using earlier version which doesn't recognize `?.` as single operator. – Tetsuya Yamamoto May 31 '17 at 13:04
  • 2
    @TetsuyaYamamoto `int` is definitely not an expression nor a variable, so it cannot be null-conditional check. There is no ambiguity. – vojta May 31 '17 at 13:06
  • 4
    Nope. not limited to C#6 doesn't work in 5 either. – Dave Becker May 31 '17 at 13:06
  • Yes, its difference occurs in the left side assignment. In C# 6 it is syntax error due to null-conditional check between int & expression, where in version 5 or earlier it even doesn't fit as `Nullable` definition or just a ternary operator. – Tetsuya Yamamoto May 31 '17 at 13:08
  • 2
    The short-hand syntax is valid in *declarations*. But this is an expression, the expression parser in C# does march to a different drummer. The curly brace languages in general do support a lot of features in expressions, not in the least by making an expression also a valid statement. It now goes looking for a lot more, operators are now valid. Which makes ? ambiguous with the conditional operator and the elvis operator, no white space required. – Hans Passant May 31 '17 at 15:50
  • 3
    @HansPassant The language spec mentions explicitly that [*“the two forms can be used interchangeably”*](https://github.com/dotnet/csharplang/blob/master/spec/types.md#nullable-types). If this is really a limitation of the expression parser and this is not actually backed by the spec, then it’s a bug in the compiler. – poke Jun 06 '17 at 15:01
  • `Nullable` has no static methods of its own. This is just a really complicated way of writing `Object.Equals`. So while the question is mildly interesting, it's not like it limits you in any way whatsoever -- just write what you mean, which, in all cases, will boil down to using `Object`. Otherwise you might mislead the reader into thinking some "specialized" or "efficient" implementation is getting called, which is not the case. – Jeroen Mostert Jun 06 '17 at 15:23
  • 1
    This works: `(int?).Equals(1, 2);`. – wablab Jun 06 '17 at 16:03

2 Answers2

12

Your MSDN quote echoes §4.1.10 of the C# 5.0 specification:

A nullable type is written T?, where T is the underlying type. This syntax is shorthand for System.Nullable<T>, and the two forms can be used interchangeably.

But “interchangeably” is an oversimplification. It’s true that T? means System.Nullable<T>, but as you’ve discovered, you can’t use T? everywhere that you can use System.Nullable<T>. In particular, the kind of member-access (§7.6.4) in your example requires a simple-name (§7.6.2):

[§7.6] Primary expressions include the simplest forms of expressions.

primary-expression:
  primary-no-array-creation-expression
  array-creation-expression

primary-no-array-creation-expression
  literal
  simple-name
  parenthesized-expression
  member-access
  ...

[§7.6.2] A simple-name is either of the form I or of the form I<A1, ..., AK>, where I is a single identifier and <A1, ..., AK> is an optional type-argument-list.

[§7.6.4] A member-access is either of the form E.I or of the form E.I<A1, ..., AK>, where E is a primary-expression, I is a single identifier and <A1, ..., AK> is an optional type-argument-list.

Nullable<T> is a simple-name and T? isn’t, so the former compiles whereas the latter doesn’t.

Why did the C# language designers require a member-access expression to use a simple-name as opposed to any type? I suppose only they can say for sure, but maybe this requirement simplified the grammar: In an expression, the compiler can assume that ? is always the conditional (ternary) operator instead of possibly a nullable type specifier.

In hindsight though, this was a fortunate choice that allowed C# 6.0 to add the ?. operator without possibly breaking existing programs. For example, consider this pathological example:

struct S
{
    public bool Equals(int x, int y) { return false; }
}

class C
{
    public static void Main()
    {
        S? S = new S();
        Console.WriteLine(S?.Equals(1, 1)); // "True" or "False"?
    }
}

Should S?.Equals be parsed as Nullable<S> . Equals, a call to the Equals static method of class Object? Or should it be parsed as S ?. Equals, a null-conditional call to the Equals instance method of variable S? Because S? isn’t a simple-name, it’s unambiguously the latter.

Michael Liu
  • 52,147
  • 13
  • 117
  • 150
0

While you're right about the syntax, the Equals method can be called from the default type using Nullable type as parameters.

You can try this unit test with whatever value you want :

int? i = 4;
int? j = null;
Assert.AreEqual(Nullable<int>.Equals(i, j), int.Equals(i, j));
Aboc
  • 237
  • 1
  • 5
  • That's because both method calls are just calling out to `object.Equals` which takes two `object` instances. These are both identical to `object.Equals`. – Servy Jun 06 '17 at 15:10
  • You're right. I was just pointing out that there is no need to try to call 'int?.Equals()'. – Aboc Jun 06 '17 at 15:18