Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
This piece of code caught me out today:
clientFile.ReviewMonth == null ? null : MonthNames.AllValues[clientFile.ReviewMonth.Value]
clientFile.Review month is a byte? and its value, in the failing case, is null.
The expected result type is string.
The exception is in this code
public static implicit operator string(LookupCode<T> code)
{
if (code != null) return code.Description;
throw new InvalidOperationException();
}
The right hand side of the evaluation is being evaluated and then implicitly converted to a string.
But my question is why is the right hand side being evaluated at all when clearly only the left hand side should be evaluated?
(The documentation states that "Only one of the two expressions is evaluated.")
The solution incidentally is to cast the null to string - that works but Resharper tells me that the cast is redundant (and I agree)
Edit: This is different to the "Why do I need to add a cast before it will compile" type ternary operator question. The point here is that no cast is required to make it compile - only to make it work correctly.
You're forgetting that implicit operators are determined at compile-time. That means that the null you have is actually of type LookupCode<T> (due to the way type inference works in a ternary operator), and needs to be cast using the implicit operator to a string; that's what gives you your exception.
void Main()
{
byte? reviewMonth = null;
string result = reviewMonth == null
? null // Exception here, though it's not easy to tell
: new LookupCode<object> { Description = "Hi!" };
result.Dump();
}
class LookupCode<T>
{
public string Description { get; set; }
public static implicit operator string(LookupCode<T> code)
{
if (code != null) return code.Description;
throw new InvalidOperationException();
}
}
The invalid operation doesn't happen on the third operand, it happens on the second - the null (actually a default(LookupCode<object>)) isn't of type string, so the implicit operator is invoked. The implicit operator throws an invalid operation exception.
You can easily see this is true if you use a slightly modified piece of code:
string result = reviewMonth == null
? default(LookupCode<object>)
: "Does this get evaluated?".Dump();
You still get an invalid operation exception, and the third operand isn't evaluated. This is of course painfully obvious in the generated IL: the two operands are two separate branches; there's no way for them both to be executed. And the first branch has another painfully obvious thing:
ldnull
call LookupCode`1.op_Implicit
It's not even hidden anywhere :)
The solution is simple: use an explicitly typed null, default(string). R# is simply wrong - (string)null isn't the same as null in this case, and R# has wrong type inference in this scenario.
Of course, this is all described in the C# specification (14.13 - Conditional operator):
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 (§13.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 (§13.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 case, an implicit conversion exists from LookupCode<T> to string, but not vice versa, so the type LookupCode<T> is preferred to string. The interesting bit is that since this is all done at compile-time, the LHS of the assignment actually makes a difference:
string result = ... // Fails
var result = ... // Works fine, var is of type LookupCode<object>
The question is not about the right argument of the ternary evaluating, it clearnly isn't (Try it, throw a different exception in the implicit operator, the code will still throw an InvalidOperationException because of ((Nullable<byte>)(null)).Value)
So the question is rather about when does the implicit cast happen. It seems that:
clientFile.ReviewMonth == null ? null : MonthNames.AllValues[clientFile.ReviewMonth.Value]
is equvivalent to
(string)(clientFile.ReviewMonth == null ? (Nullable<LookupCode<byte>>)null : (Nullable<LookupCode<byte>>)MonthNames.AllValues[clientFile.ReviewMonth.Value]);
rather than
clientFile.ReviewMonth == null ? (string)null : (string)MonthNames.AllValues[clientFile.ReviewMonth.Value]);
So resharper is simply wrong here.
Related
I am trying to understand whats happening and why it doesnt work. I was trying to convert "34" to the int 34. I wish to know why there is a difference (and why my version didnt work):
My version:
var cell = GetSpecificString();
return (cell == "") ? null : int.Parse(cell);
SO version:
var cell = GetSpecificString();
if (int.TryParse(cell, out int i)) return i;
return null;
Any tips would be greatly appreciated, as I want to know whats happening here.
Does not work implies here that the Intellisense is complaining because there is no conversion between '' and 'int'. But I want to return a nullable int, so this seems weird to me.
The first version doesn't work because the conditional operator doesn't allow it whereas the second approach uses an if. The rules for the conditional operator are stricter. Both types must be compatible but null and int are not. You could achieve it by using:
return (cell == "") ? (int?)null : int.Parse(cell);
However, i'd really use int.TryParse since an empty string is not the only invalid value:
return int.TryParse(cell, out int parsedValue) ? parsedValue : new int?();
There is already good explanation on stack overflow for this problem:
For complete explanation refer Type of conditional expression cannot be determined because there is no implicit conversion between 'int' and <null>
The spec (§7.14) says that for conditional expression b ? x : y, there are three possibilities, either x and y both have a type and certain good conditions are met, only one of x and y has a type and certain good conditions are met, or a compile-time error occurs.
If only one of x and y has a type, and both x and y are implicitly convertible to that type, then that is the type of the conditional expression.
So I came across some very baffling behavior of the native ternary operator in C# today. The ternary operator is sending the wrong data type to the method I'm calling. The basic premise is I want to convert the decimal value to an int if decimalEntry == false so it will get stored in a database as an int Here is the code:
decimal? _repGroupResult = 85.00
int? intValue = null;
bool decimalEntry = false;
if (decimalEntry == false)
{
intValue = (int?) _repGroupResult;
}
Console.WriteLine("Sending to ResultAdd via ternary operator");
RepGBParent.ResultAdd(this.RepInfo.ResultID, decimalEntry ? _repGroupResult : intValue);
Console.WriteLine("Sending to ResultAdd via if statement");
// All other tests - just add the rep group result
if (decimalEntry)
{
RepGBParent.ResultAdd(this.RepInfo.ResultID, _repGroupResult);
}
else
{
RepGBParent.ResultAdd(this.RepInfo.ResultID, intValue);
}
The method I'm calling ResultAdd is here:
public void ResultAdd(int pResultID, object pResultValue)
{
if (pResultValue == null) { return; }
Console.WriteLine(this.TestInfo.TestNum + ": " + pResultValue.GetType());
....
}
The ternary operator receives a decimal while the if statement sends an int. As shown by the output code below:
I consider myself a programmer of reasonable talent and this really threw me back today. I played around with it for 2-3 hours and figured out the best way to post it here so I'm clear of the issue I'm having.
Please avoid "why are you even doing it that way" type responses. I simply want to know why there is a difference here between the ternary operator and the if statement.
The only other post that I found that was closely related was this one but it didn't quite match up:
Bizarre ternary operator behavior in debugger on x64 platform
The ternary operator is - well - an operator which is only a special kind of method. And as any other method, it can only have one single return type.
What you try to do is to use the operator to return a decimal? or an int? depending on a condition. That is not possible.
What happens is that the compiler knows that there is an implicit conversion from int? to decimal?, but not the other way round. So it infers the return type of the operator as decimal? and implicitly converts your intValue into a decimal?.
The ternary expression returns a single type, not a type conditional on the result of the evaluation.
Your int is promoted to decimal in order to fullful that requirement.
If the conversion could not be applied, you would get a complier error.
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.
https://msdn.microsoft.com/en-us/library/ty67wk28.aspx
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Conditional operator assignment with Nullable<value> types?
Why does the conditional operator “?:” not work here when my function is returning a nullable integer “int?”? “return null” works but with “?:” I have to cast “null” to “(int?)” first.
public int? IsLongName(string name) {
int length = name.Length;
// this works without problems
if (name.Length > 10) {
return null;
} else {
return name.Length;
}
// this reports:
// Type of conditional expression cannot be determined because
// there is no implicit conversion between '<null>' and 'int'
return name.Length > 10 ? null : name.Length;
}
Try changing your last line to this:
return name.Length > 10 ? null : (int?)name.Length;
The compiler can't understand what's the return type of the ?: operator. It has conflicting values - null and int. By casting the int to nullable, the compiler can understand the return type is nullable int, and null would be accepted as well.
Both a null value and an int value can be implicitly converted to an int? data type, but a literal of null on its own isn't known by the compiler to be anything other than an object if you don't tell it. There is no common data type that both object and int can be implicitly converted to, which is what the compiler is complaining about.
As Yorye says, you can cast the int to int? to let the compiler do the conversion; or, you can cast the null to int?, which then allows the compiled to use the implicit conversion from int to int?.
Both conditions of the ?: operator must be implicitly compatible. int can never be null, so there is a compile-time error (likewise, null can never be int). You have to cast, there's no way around it using the ternary.
I don't think you would have the same problem with an if statement, because the compiler would only check that the method returns a compatible value to the return type from any given path, not that the return value from any given exit point is implicitly compatible with another block's return value.
The ?: operator only considers the types of its two possible return values. It is not aware of the type of the variable that will receive its result (indeed, in more complex expressions, no explicit variable may exist).
If one of those return values is null, it has no type information - it can only check the other return value's type, and check whether a conversion exists.
In your case, we have null and a return value of type int. There is no conversion available. There would be a conversion to int?, but that is neither of the possible return types that ?: is considering.
I am trying to use the conditional operator, but I am getting hung up on the type it thinks the result should be.
Below is an example that I have contrived to show the issue I am having:
class Program
{
public static void OutputDateTime(DateTime? datetime)
{
Console.WriteLine(datetime);
}
public static bool IsDateTimeHappy(DateTime datetime)
{
if (DateTime.Compare(datetime, DateTime.Parse("1/1")) == 0)
return true;
return false;
}
static void Main(string[] args)
{
DateTime myDateTime = DateTime.Now;
OutputDateTime(IsDateTimeHappy(myDateTime) ? null : myDateTime);
Console.ReadLine(); ^
} |
} |
// This line has the compile issue ---------------+
On the line indicated above, I get the following compile error:
Type of conditional expression cannot be determined because there is no implicit conversion between '< null >' and 'System.DateTime'
I am confused because the parameter is a nullable type (DateTime?). Why does it need to convert at all? If it is null then use that, if it is a date time then use that.
I was under the impression that:
condition ? first_expression : second_expression;
was the same as:
if (condition)
first_expression;
else
second_expression;
Clearly this is not the case. What is the reasoning behind this?
(NOTE: I know that if I make "myDateTime" a nullable DateTime then it will work. But why does it need it?
As I stated earlier this is a contrived example. In my real example "myDateTime" is a data mapped value that cannot be made nullable.)
The compiler does not infer the type of the result of the conditional operator from the usage of the result, but from the types of its arguments. The compiler fails when it sees this expression because it cannot deduce the type of the result:
IsDateTimeHappy(myDateTime) ? null : myDateTime;
Since null and DateTime are not compatible, you need to tell the compiler what the type should be. A cast should do the trick:
DateTime? x = IsDateTimeHappy(myDateTime) ? (DateTime?)null : myDateTime;
OutputDateTime(x);
Now the compiler will have no problems. You can also write the above on one line if you prefer (but I would probably not do this):
OutputDateTime(IsDateTimeHappy(myDateTime) ? (DateTime?)null : myDateTime);
Eric Lippert has a good answer that is also relevant here and goes into more details about how the compiler determines types.
The reason is the ternary operator expects both the operands to be of the same type. The whole operator get worked out BEFORE it is assigned to a result (in this case passed into a function), so the compiler can't know what the result type is.
IsDateTimeHappy(myDateTime) ? null : myDateTime
In the above case there is no conversion path between null and DateTime. As soon as you cast one of them to DateTime?, the compiler can convert the other one:
IsDateTimeHappy(myDateTime) ? (DateTime?)null : myDateTime
//OR
IsDateTimeHappy(myDateTime) ? null : (DateTime?)myDateTime
The fist line of code above works because the compiler can convert DateTime to DateTime? via an implicit conversion operator:
//In Nullable<T>
public static implicit operator T?(T value);
The second line works because null can be assigned to DateTime? since the latter is a reference type.
The implicit conversion is not allowed by the return statement. If you had
if (condition)
return first_expression;
else
return second_expression;
Then you'd be comparing apples to apples. And you'd have no problems - as you stated.
In your case, you're allocated so much space on the stack for a DateTime - which is a non-nullable value type. So you're making a statement that doesn't make any sense to the compiler. If you say, I'm going to pass you an A or a B, then the A and the B need to be the same thing. In your case, the B can never be an A.
What the compiler is saying is:
If IsDateTimeHappy(myDateTime) is false, then I need to return a value of type DateTime equal to myDateTime. If it is true, then I need to return a value equal to null, but you haven't told me what type it should be!
That's why Mark's answer is the solution. After you provide a cast telling the compiler what type of value will be returned if the condition is true, it can check whether the true and false return values can be converted to (or are of) the same type.
Cheers Mark! ;-)
Instead of null use default(DateTime?) and then both sides of the ternary will have compatible types.
I've noticed that some .NET structs can be compared to null.
For example:
TimeSpan y = new TimeSpan();
if (y == null)
return;
will compile just fine (the same with the Guid struct).
Now I know that stucts are value type and that the code above should not compile, unless there's an overload of operator == which takes an object. But, as far as I could tell there isn't.
I've looked at the class with Reflector, and also at the docs on MSDN.
The two of them do implement the following interfaces:
IComparable, IComparable<T>, IEquatable<T>
but, trying to implment the same Interfaces did not seem to help:
struct XX : IComparable, IComparable<XX>, IEquatable<XX> {
public int CompareTo(Object obj) {
return 0;
}
public int CompareTo (XX other){
return 0;
}
public bool Equals (XX other){
return false;
}
public override bool Equals(object value){
return false;
}
public static int Compare(XX t1, XX t2){
return 0;
}
}
I'm using: .NET 2.0 Visual Studio 2005.
Does anyone has any idea what's the reason for this ?
I am just trying to get a better understanding. This isn't an issue as I know I shouldn't compare structs to null anyway.
It's the == operator.
The TimeSpan class has an overload of the equality operator:
public static bool operator ==(DateTime d1, DateTime d2)
{
return (t1._ticks == t2._ticks);
}
This in itself doesn't make it possible to compare with null, but...
With the arrival of nullable types, each struct is implicitly convertible to its nullable type, so when you see something like
TimeSpan y = new TimeSpan();
if (y == null)
return;
You don't see that this is happening:
TimeSpan y = new TimeSpan();
if ((Nullable<TimeSpan>)y == (Nullable<TimeSpan>)null)
return;
Null gets the implicit conversion (implicit assignment?), but not all System.Object objects do:
TimeSpan y = new TimeSpan();
object o = null;
if (y == o) //compiler error
return;
Okay, but the equality operator doesn't take nullable arguments, does it?
Well, msdn is of help here, stating:
The predefined unary and binary
operators and any user-defined
operators that exist for value types
may also be used by nullable types.
These operators produce a null value
if [any of] the operands are null; otherwise,
the operator uses the contained value
to calculate the result.
So you effectively get a nullable implementation for each operator for free, with a fixed defined behaviour. The "contained value" mentioned above is the actual value the non-nullable operator would return.
This problem was effectively introduced when nullable types were included. There's an implicit conversion from TimeSpan to TimeSpan?, and there's a comparison between TimeSpan? and the null value of that type.
The compiler issues a warning for some types which makes it clearer what it's trying to do:
int x = 10;
if (x == null)
{
Console.WriteLine();
}
Gives this warning:
Test.cs(9,13): warning CS0472: The result of the expression is always 'false'
since a value of type 'int' is never equal to 'null' of type 'int?'
I believe Marc Gravell and I worked out the circumstances under which the warning is given once... it's a shame it's not consistent.
This case is covered for generics in section 7.9.6 of the C# language specification.
The x == null construct is permitted even though T could represent a value type, and the result is simply defined to be false when T is a value type.
I dug through the spec for a bit and couldn't find a more general rule. Jon's answer indicates it's a nullable promotion issue.
This rule (or a similar variation) does seem to be being applied here. If you look at the reflected output closely you'll notice the comparison isn't there. The C# compiler is apparently optimizing this comparison away and replacing it with false.
For instance, if you type the following
var x = new TimeSpan();
var y = x == null;
Console.WriteLine(x);
Then decompile it you'll see the following
var x = new TimeSpan();
var y = false;
Console.WriteLine(x);
See also: C# 3 (.NET 3.5) version of csc fails to report CS0162 for unrechable code (struct/null)
Starting with the C# 3 compiler that means it sometimes doesn't even warn you about this ;-p
Because Guid / TimeSpan etc provide ==, they fall into this trap where it doesn't warn you.
I FOUND IT :)
The following gives a warning:
int i = 0;
if (i == null)
// ^^ Warning: The result of the expression is always 'false' since a value of
// type 'int' is never equal to 'null' of type 'int?'
The compiler is just failing to emit the correct warning that the null you typed was converted to type TimeSpan? for the comparison.
Edit: The related section in the spec is §13.7.1 stating that null can be implicitly converted to any nullable type, and (the very difficult to read) section §13.7.2 stating a value type T can be implicitly converted to T?.
What I originally wrote:
Whatever's happening is something in the C# spec because like JaredPar says it compiles to simply false.
Note that this doesn't compile:
TimeSpan ts = new TimeSpan();
object o = null;
if (ts == o) // error, Operator '==' cannot be applied to operands of type 'System.TimeSpan' and 'object'
...