I discovered a strange behaviour of my program and after futher analysis, I was able to find that there is probably something wrong either in my C# knowledge or somewhere else. I beleive it's my mistake but I cannot find an 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 is evaluated to null. I don't get why is it null.
I googled and found a response in this question - Implicit casting of Null-Coalescing operator result - with the official specification.
But following this specification, I can't find the reason why "b" is null :( Maybe I'm reading it wrong in which case I apologize for spamming.
If A exists and is not a nullable type or a reference type, a compile-time error occurs.
...that's not the case.
If b is a dynamic expression, the result type is dynamic. At run-time, 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.
...that's also not the case.
Otherwise, if A exists and is a nullable type and an implicit conversion exists from b to A0, the result type is A0. At run-time, a is first evaluated. If a is not null, a is unwrapped to type A0, and this becomes the result. Otherwise, b is evaluated and converted to type A0, and this becomes the result.
...A exists, implicit conversion from b to A0 does not exist.
Otherwise, if A exists and an implicit conversion exists from b to A, the result type 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, implicit conversion from b to A does not exist.
Otherwise, if b has a type B and an implicit conversion exists from a to B, the result type is B. At run-time, a is first evaluated. If a is not null, a is unwrapped to type A0 (if A exists and is nullable) and converted to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.
...b has a type B and implicit conversion exists from a to B.
a is evaluated into null. Therefore, b should be evaluated and b should be the result.
Otherwise, a and b are incompatible, and a compile-time error occurs.
Does not happen
Am I missing something please?
Why did you expect the null-coalescing operator to return new B()? a is not null, so 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 we need to cast a to T.
• Otherwise, if b has a type B and an implicit conversion exists from
a to B, the result type is B. At run-time, a is first evaluated. If a
is not null, a is unwrapped to type A0 (if A exists and is nullable)
and 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 result type of the expression. Which means a will be implicitly casted to B. And your implicit operator returns null.
In fact, if you write var b = a ?? new B(); (notice the var), you'll see that the compiler infers B to be the type returned by the expression.
Otherwise, if b has a type B and an implicit conversion exists from a
to B, the result type is B. At run-time, a is first evaluated. If a is
not null, a is unwrapped to type A0 (if A exists and is nullable) and
converted to type B, and this becomes the result. Otherwise, b is
evaluated and becomes the result.
...b has a type B and implicit conversion exists from a to B. a is
evaluated into null. Therefore, b should be evaluated and b should be
the result.
You're interpreting this one wrong. Nothing says that a to B conversion is done before null check is performed. It states that null check is done before the conversion!
Your case fits to that:
If a is not null, a is unwrapped to type A0 (if A exists and is
nullable) and converted to type B, and this becomes the
result.
Well, the specification says (I change to x and y for less confusion here):
• Otherwise, if y has a type Y and an implicit conversion exists from x to Y, the result type is Y. At run-time, x is first evaluated. If x is not null, x is unwrapped to type X0 (if X exists and is nullable) and converted to type Y, and this becomes the result. Otherwise, y is evaluated and becomes the result.
This happens. First, the left-hand side x, which is just a, is checked for null. But it is not null in itself. Then the left-hand side is to be used. The implicit conversion is then run. Its result of type B is ... null.
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 is null in itself, and the result becomes the right-hand side (y).
Maybe implicit conversions between reference types should return null (if and) only if the original is null, as a good practice?
I guess the guys who wrote the spec could have done it like this (but did not):
• Otherwise, if y has a type Y and an implicit conversion exists from x to Y, the result type is Y. At run-time, x is first evaluated and converted to type Y. If the output of that conversion is not null, that output becomes the result. Otherwise, y is evaluated and becomes the result.
Maybe that would have been more intuitive? It would have forced the runtime to call your implicit conversion no matter if the input to the conversion were null or not. That should not be too expensive if typical implementations quickly determined that null → null.
The part we need to look at is the compile-time type of the null-coalescing expression.
Otherwise, if b has a type B and an implicit conversion exists from a to B, the result type is B. At run-time, a is first evaluated. If a is not null, a is unwrapped to type A0 (if A exists and is nullable) and converted to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.
To put this into pseudocode:
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);
}
...
}
Related
If we use the == operator between an expression of ulong and an expression of ulong?, then the operator overload bool ulong(ulong left, ulong right) appears to be used.
In other words, the operator considers both expressions non-null.
In this sample program, equal correctly becomes false, with no exceptions.
void Main()
{
var temp = new Temp(0);
object temp2 = null;
var equal = temp.Id == (temp2 as Temp)?.Id; // False :) but how?
}
public class Temp
{
public ulong Id {get;}
public Temp(ulong id)
{
this.Id = id;
}
}
How can this not throw? There is no conversion from a ulong? with value null to a ulong. (ulong)(ulong?)null throws: "Nullable object must have a value."
Does this return the correct value for every possible value of ulong?, including null? If so, how? The type ulong? has one more possible value than ulong, so there should be one set of two values that map to the same ulong value, which would introduce one false positive "true" result.
In theory, I could imagine null being coalesced to default(ulong), but then the result in my example above would be true, which would be an incorrect answer. And as we can see, the compiler does not make that mistake - it answers correctly.
From MSDN:
Lifted operators permit predefined and user-defined operators that operate on non-nullable value types to also be used with nullable forms of those types. Lifted operators are constructed from predefined and user-defined operators that meet certain requirements, as described in the following:
...
For the equality operators
== !=
a lifted form of an operator exists if the operand types are both non-nullable value types and if the result type is bool. The lifted form is constructed by adding a single ? modifier to each operand type. The lifted operator considers two null values equal, and a null value unequal to any non-null value. If both operands are non-null, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.
You're not using the operator:
bool ==(ulong left, ulong right)
You're using the lifted operator:
bool ==(ulong? left, ulong? right)
This operator takes two ulong? parameters, and returns true if both are null, or if both are non-null and have the same value.
You're probably looking at Visual Studio, which does show you something confusing in this case:
Don't be confused by this -- as #mjwills pointed out in the comments, this is a known issue.
If you write this:
public bool M(ulong a, ulong? b) {
return a == b;
}
Then the compiler produces the following code:
public bool M(ulong a, ulong? b)
{
ulong? num = b;
return (a == num.GetValueOrDefault()) & num.HasValue;
}
num.GetValueOrDefault() returns 0 if b is null, otherwise the value of b. So M returns true if and only if b is not null, and has the same value as a.
SharpLab
If we use the == operator between an expression of ulong and an
expression of ulong?, then the operator overload bool ulong(ulong
left, ulong right) is used.
A large part of the issue is that Visual Studio's intellisense incorrectly shows ulong's == operator being used (if you hover over ==).
This is a bug, as per https://github.com/dotnet/roslyn/issues/21494 . It is not actually using that operator.
Since all of your other questions are based on that faulty premise, realising the existence of that bug makes the other issues largely disappear.
Why does the following crash with a NullReferenceException on the statement a.b.c = LazyInitBAndReturnValue(a);?
class A {
public B b;
}
class B {
public int c;
public int other, various, fields;
}
class Program {
private static int LazyInitBAndReturnValue(A a)
{
if (a.b == null)
a.b = new B();
return 42;
}
static void Main(string[] args)
{
A a = new A();
a.b.c = LazyInitBAndReturnValue(a);
a.b.other = LazyInitBAndReturnValue(a);
a.b.various = LazyInitBAndReturnValue(a);
a.b.fields = LazyInitBAndReturnValue(a);
}
}
Assignment expressions are evaluated from right to left, so by the time we are assigning to a.b.c, a.b should not be null. Oddly enough, when the debugger breaks on the exception, it too shows a.b as initialized to a non-null value.
This is detailed in Section 7.13.1 of the C# spec.
The run-time processing of a simple assignment of the form x = y
consists of the following steps:
If x is classified as a variable:
x is evaluated to produce the variable.
y is evaluated and, if required, converted to the type of x through an implicit conversion (Section 6.1).
If the variable given by x is an array element of a reference-type, a run-time check is performed to ensure that the value
computed for y is compatible with the array instance of which x is an
element. The check succeeds if y is null, or if an implicit reference
conversion (Section 6.1.4) exists from the actual type of the instance
referenced by y to the actual element type of the array instance
containing x. Otherwise, a System.ArrayTypeMismatchException is
thrown.
The value resulting from the evaluation and conversion of y is stored into the location given by the evaluation of x.
If x is classified as a property or indexer access:
The instance expression (if x is not static) and the argument list (if x is an indexer access) associated with x are evaluated, and the results are used in the subsequent set accessor invocation.
y is evaluated and, if required, converted to the type of x through an implicit conversion (Section 6.1).
The set accessor of x is invoked with the value computed for y as its value argument.
I think the bottom section (if x is classified as a property or indexer access) provides a hint, but perhaps a C# expert can clarify.
A set accessor is generated first, then y is evaluated (triggering your breakpoint), then the set accessor is invoked, which causes a null reference exception. If I had to guess, I'd say the accessor points to the old value of b, which was null. When you update b, it doesn't update the accessor that it already created.
I realize this doesn't answer your question, but allowing something outside of class A to initialize a member belonging to class A in this fashion seems to me to break encapsulation. If B needs to be initialized on first use the "owner" of B should be the one to do that.
class A
{
private B _b;
public B b
{
get
{
_b = _b ?? new B();
return _b;
}
}
}
From the MSDN documentation, the following two snippets are equal:
bool value;
int x = (value) ? 0 : 1;
And
bool value;
int x;
if (value)
x = 0;
else
x = 1;
Great, wonderful. I use it all the time. Terse and effective.
If we try this with a nullable type, like so:
int? x = (value.HasValue) ? value.Value : null;
We get a compile-time error:
The type of conditional expression cannot be determined
because there is no implicit conversion between '{NullableType}' and null.
This compiles fine:
int? value;
int? x;
if (value.HasValue)
x = value.Value;
else
x = null;
So, I understand that the compiler requires an explicit cast in the way of (int?)null to compile the first statement. What I don't understand is why it is required in that statement, but not the If Else block.
null can represent any object-based datatype. You need to cast null as a datatype so it know what you are talking about.
int? x = (value.HasValue) ? value.Value : (int?)null;
I know, it sounds a bit strange.
To answer the questions in the comments:
Why is it not implicit though?
Yeah, I get that. But why do I not have to cast it in a If Else block?
Let's walk through the code.
Your else statement looks like this:
else x = null;
This means you are assigning the value of null to x. This is valid, because x is a int?, which takes nulls.
The difference comes when you have the ternary operator. It says: "assign the value of the operator into x". The question (and the reason for your error) is, what datatype is the result of the ternary operator?
From your code, you can't be sure, and the compiler throws its hands up.
int? x = (value.HasValue) ? value.Value : null;
// int? bool int ??
What datatype is null? You are quick to say "well it's a int?, because the other side is a int and the result is a int?". The problem is, what about the following:
string a = null;
bool? b = null;
SqlConnectionStringBuilder s = null;
This is also valid, which means null can be used for any object-based datatype. This is why you have to explicitly cast null as the type you want to use, because it can be used for anything!
Another explanation (and possible more accurate):
You can't have an implicit cast between a nullable and a non-nullable value.
int is not-nullable (it's a structure), where null is. This is why in Habib's answer you can put the cast on either the left or right side.
For Condtional operator MSDN states:
Either the type of first_expression and second_expression must be
the same, or an implicit conversion must exist from one type to
the other.
So in you case your first_expression and second_expression are:
int? x = (value.HasValue) ? value.Value : null;
^^^^^^^^ ^^^^^
first exp 2nd Exp
Now if you see, your First Expression is of type int, and second expression is null and both are not same and there is no implicit conversion. So casting any of them to `int? solves the problem.
So:
int? x = (value.HasValue) ? (int?) value.Value : null;
Or
int? x = (value.HasValue) ? value.Value : (int?) null;
are fine.
Now why it is not required with if-else, because there multiple statements involved and it not a single statement assigning the value.
var x = value.HasValue ? value.Value : default(int?);
works too.
The documentation for the ?: operator states that the type of the expression b ? x : y is determined by examining the types of x and y:
If X and Y are the same type, then this is the type of the conditional expression.
Otherwise, if an implicit conversion (Section 6.1) exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
Otherwise, if an implicit conversion (Section 6.1) exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
Otherwise, no expression type can be determined, and a compile-time error occurs.
In your example
int? x = (value.HasValue) ? value.Value : null;
there is no implicit conversion between int and null so the last bullet applies.
The reason is that the type of conditional expression is determined by the second and third operators of the conditional operator (?:) .
Since null has no type, the compiler cannot determine the type of the overall expression so it emits a compiler error.
The reason it works with the simple assignment operator (=) is because the left side of the operator determines the the type. Since in the If statement, the type of x is already known, the compiler doesn't complain.
See section 7.14 (Conditional operator) and section 7.17.1 (Simple assignment) for further explanations.
Can someone please explain me the logic reason why I must cast null to int?
When the left argument type can have their both types ?
Insted of doing
int? k = (DateTime.Now.Ticks%5 > 3 ? 1 : null);
I must do
int? k = (DateTime.Now.Ticks%5 > 3 ? 1 : (int?) null);
although int? k = null is perfectly valid.
An opposite example :
I didn't have to do it in:
string k = (DateTime.Now.Ticks%5 > 3 ? "lala" : null);
int? k = (DateTime.Now.Ticks%5 > 3 ? 1 : (int?) null);
In this case what we have is 1 is int and null is actually null
Now the confusion is the ternary operator is confused as what is the return type int or well null and since its int it won't accept null
So you would need to cast it to a nullable int
Now in the other case you have a string and null is perfectly acceptable for a string
Further explanation can be found at Type inference - Eric
The second and third operands of the ?: operator control the type of
the conditional expression. Let X and Y be the types of the second and
third operands. Then,
If X and Y are the same type, then this is the type of the conditional expression.
Otherwise, if an implicit conversion exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
Otherwise, if an implicit conversion exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
Otherwise, no expression type can be determined, and a compile-time error occurs.
Because the compiler doesn't use the type of the variable in the left hand side to determine the type of the expression on the right hand side. First it determines what type the expression is, then it determines if it's possible to put it in the variable.
There is no type close enough that is common between an int and a null value. You have to either make the int value nullable or the null value "intable" for the compiler to find a common ground for the values.
When you have a string and a null value the compiler can simply use one of the types, because a string is already nullable.
int? x = null;
x = x + 1; // Works, but x remains null
I would expect the compiler to attempt to cast x as an int, but apparently it does not.
Edit by 280Z28: Changed NullReferenceException to InvalidOperationException, which is what Nullable<T>.Value throws when HasValue is false.
This is per the specification for lifted binary operators. From §7.2.7:
For the binary operators
+ - * / % & | ^ << >>
a lifted form of an operator exists if the operand and result types are all non-nullable value types. The lifted form is constructed by adding a single ? modifier to each operand and result type. The lifted operator produces a null value if one or both operands are null (an exception being the & and | operators of the bool? type, as described in §7.10.3). Otherwise, the lifted operator unwraps the operands, applies the underlying operator, and wraps the result.
The reasoning is this: you are to think of null for a nullable type as meaning "I do not know what the value is." What is the result of "I don't know" plus one? "I don't know." Thus, the result should be null.
Nullables are never actually null references. They are always object references. Their internal classes override the == and = operators. If they are being compared to null, they'll return the value of the HasValue property.
Why would you expect the compiler to cast it as int when you've declared it as Nullable? The compiler is doing what you've told it to do and null +1 = null.
You'll have to cast explicitly or check x.HasValue before attempting to add an int.
The reason for this is that the compiler creates a 'lifted' operator for nullable types - in this case it is something like:
public static int? operator +(int? a, int? b)
{
return (a == null || b == null) ? (int?)null : a.Value + b.Value
}
I think if you try to assign the result to a non-nullable value, the compiler will be forced to use the non-nullable overload and convert x to an int.
e.g. int i = x + 1; //throws runtime exception
Unfortunately it doesn't. The X in x = X + 1 is null as in the first line so you're adding 1 to null, which equals null.
As it's a nullable int, you can use x.HasValue to check if it has a value, and then x.Value to get the actual int value out
Regardless of whether x is actually never null, that's not even the point.
The point is, when have you ever seen a NullReferenceException when trying to perform an addition?
The following example doesn't throw a NullReferenceException either and is perfectly valid.
string hello = null;
string world = "world";
string hw = hello+world;
You would only get a NullReferenceException if you try to access a member on an object that is null.
int? can never be null because it is a struct. Structs live on the stack and the stack does not handle null well.
See What is a NullPointerException, and how do I fix it?
Also, the nullable types have 2 very useful properties : HasValue, Value
This code:
if (x != null)
{
return (int) x;
}
Should be refactored to this:
if (x.HasValue)
{
return x.Value;
}