Note: this appears to have been fixed in Roslyn
This question arose when writing my answer to this one, which talks about the associativity of the null-coalescing operator.
Just as a reminder, the idea of the null-coalescing operator is that an expression of the form
x ?? y
first evaluates x
, then:
- If the value of
x
is null,y
is evaluated and that is the end result of the expression - If the value of
x
is non-null,y
is not evaluated, and the value ofx
is the end result of the expression, after a conversion to the compile-time type ofy
if necessary
Now usually there's no need for a conversion, or it's just from a nullable type to a non-nullable one - usually the types are the same, or just from (say) int?
to int
. However, you can create your own implicit conversion operators, and those are used where necessary.
For the simple case of x ?? y
, I haven't seen any odd behaviour. However, with (x ?? y) ?? z
I see some confusing behaviour.
Here's a short but complete test program - the results are in the comments:
using System;
public struct A
{
public static implicit operator B(A input)
{
Console.WriteLine("A to B");
return new B();
}
public static implicit operator C(A input)
{
Console.WriteLine("A to C");
return new C();
}
}
public struct B
{
public static implicit operator C(B input)
{
Console.WriteLine("B to C");
return new C();
}
}
public struct C {}
class Test
{
static void Main()
{
A? x = new A();
B? y = new B();
C? z = new C();
C zNotNull = new C();
Console.WriteLine("First case");
// This prints
// A to B
// A to B
// B to C
C? first = (x ?? y) ?? z;
Console.WriteLine("Second case");
// This prints
// A to B
// B to C
var tmp = x ?? y;
C? second = tmp ?? z;
Console.WriteLine("Third case");
// This prints
// A to B
// B to C
C? third = (x ?? y) ?? zNotNull;
}
}
So we have three custom value types, A
, B
and C
, with conversions from A to B, A to C, and B to C.
I can understand both the second case and the third case... but why is there an extra A to B conversion in the first case? In particular, I'd really have expected the first case and second case to be the same thing - it's just extracting an expression into a local variable, after all.
Any takers on what's going on? I'm extremely hesistant to cry "bug" when it comes to the C# compiler, but I'm stumped as to what's going on...
EDIT: Okay, here's a nastier example of what's going on, thanks to configurator's answer, which gives me further reason to think it's a bug. EDIT: The sample doesn't even need two null-coalescing operators now...
using System;
public struct A
{
public static implicit operator int(A input)
{
Console.WriteLine("A to int");
return 10;
}
}
class Test
{
static A? Foo()
{
Console.WriteLine("Foo() called");
return new A();
}
static void Main()
{
int? y = 10;
int? result = Foo() ?? y;
}
}
The output of this is:
Foo() called
Foo() called
A to int
The fact that Foo()
gets called twice here is hugely surprising to me - I can't see any reason for the expression to be evaluated twice.
C? first = ((B?)(((B?)x) ?? ((B?)y))) ?? ((C?)z);
. You'll get:Internal Compiler Error: likely culprit is 'CODEGEN'
– configurator(("working value" ?? "user default") ?? "system default")
– Factor Mystic