Implicit conversion with null coalescing operator

I discovered the strange behavior of my program, and after further analysis, I was able to find that there is probably something wrong both in my knowledge of C # and elsewhere. I believe that this is my mistake, but I can not find the answer anywhere ...

public class B { public static implicit operator B(A values) { return null; } } public class A { } public class Program { static void Main(string[] args) { A a = new A(); B b = a ?? new B(); //b = null ... is it wrong that I expect b to be B() ? } } 

The variable "b" in this code evaluates to null. I do not understand why this is null.

I googled and found the answer in this question - Implicit casting of the result of the Null-Coalescing operator - with the official specification.

But after this specification, I can’t find the reason why β€œb” is null :( Maybe I am reading this incorrectly, in which case I apologize for the spam.

If A exists and is not a NULL or reference type, a compile-time error occurs.

... this is not the case.

If b is a dynamic expression, the type of the result is dynamic. At runtime, a is first evaluated. If a is not null, a is converted to dynamic, and this becomes the result. Otherwise, b is evaluated, and this becomes the result.

... this is not the case either.

Otherwise, if A exists and is of type NULL, and there is an implicit conversion from b to A0, then the result is A0. At runtime, a is first evaluated. If a is not null, a is expanded to enter A0, and this becomes the result. Otherwise, b is evaluated and converted to type A0, and this becomes the result.

... A exists, an implicit conversion from b to A0 does not exist.

Otherwise, if A exists and there is an implicit conversion from b to A, then the result is A. At run time, a is first evaluated. If a is not null, a becomes the result. Otherwise, b is evaluated and converted to type A, and this becomes the result.

... A exists, the implicit conversion from b to does not exist.

Otherwise, if b is of type B and there is an implicit conversion from a to B, then the result is B. At run time, a is first evaluated. If a is not null, a is expanded to enter A0 (if A exists and is NULL) and converted to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.

... b is of type B and there is an implicit conversion from a to B. a is evaluated as null. Therefore, b must be evaluated and b must be the result.

Otherwise, a and b are incompatible, and a compile-time error occurs. Not happening

Am I missing something?

+6
source share
4 answers

Well, the spec states (I change to x and y for less confusion here):

β€’ Otherwise, if y is of type Y and there is an implicit conversion from x to Y, the result is Y. At runtime, x is evaluated first. If x is not null, x is unpacked for type X0 (if X exists and is null) and converted to type Y, and this becomes the result. Otherwise, y is computed and becomes the result.

It happens. First, the left side of x , which is just a , is checked for null . But this is not null itself. Then the left side is used . Then an implicit conversion is performed. Its result of type B is ... null .

Please note that this is different from:

  A a = new A(); B b = (B)a ?? new B(); 

In this case, the left operand is an expression ( x ), which itself is null , and the result becomes the right side ( y ).

Perhaps implicit conversions between reference types should return null (if and) only if the original is null , as good practice?


I think the guys who wrote the spec could do it this way (but didn't):

β€’ Otherwise, if y is of type Y and there is an implicit conversion from x to Y, then the result will be Y. At run time, x is first evaluated and converted to type Y. If the output of this conversion is not null, this result becomes the result. Otherwise, y is computed and becomes the result.

Perhaps this would be more intuitive? This forced the runtime to invoke an implicit conversion, regardless of whether the input to the conversion was null or not. This should not be too expensive if typical implementations quickly determined that null β†’ null .
+1
source

Why did you expect the zero-coalescent operator to return new B() ? a not null, therefore a ?? new B() a ?? new B() evaluates to a .

Now that we know that a will be returned, we need to determine the type of the result ( T ) and whether to discard a in T

β€’ Otherwise , if b is of type B and there is an implicit conversion from a to B, the result is B .. At runtime, a is first evaluated. If it is not null, a is expanded to enter A0 (if A exists and is null) and is converted to type B, and this becomes the result . Otherwise, b is evaluated and becomes the result.

An implicit conversion exists from a to B , so B is the type of the result of the expression. This means that a will be implicitly selected on B And your implicit statement returns null .

In fact, if you write var b = a ?? new B(); var b = a ?? new B(); (notice var ), you will see that the compiler tells B as the type returned by the expression.

+8
source

Otherwise, if b is of type B and there is an implicit conversion from to B, the result type is B. At run time, a is first evaluated. If a is not null, a expands for type A0 (if A exists and is zero) and converts to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.

... b is of type B and there is an implicit conversion from a to B. a is evaluated to null. Therefore, b should be evaluated, and b should be the result.

You misinterpret it. Nothing suggests that converting a to B is done before the null check. It states that the null check is done before the conversion!

Your case is suitable for this:

If a is not null , a expands to enter A0 (if A exists and exists nullable) and converts to type B , and this becomes the result .

+4
source

The part we need to consider is the type of compilation time of the null coalescing expression.

Otherwise, if b is of type B and there is an implicit conversion from a to B, then the result is B. At run time, a is first evaluated. If a is not null, a is expanded to enter A0 (if A exists and is NULL) and converted to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.

To put this in pseudo code:

 public Tuple<Type, object> NullCoalesce<TA, TB>(TA a, TB b) { ... else if (a is TB) // pseudocode alert, this won't work in actual C# { Type type = typeof(TB); object result; if (a != null) { result = (TB)a; // in your example, this resolves to null } else { result = b; } return new Tuple<Type, object>(type, result); } ... } 
0
source

All Articles