Can anyone please explain to me what happens behind the scenes when you use ternary operator?
does this line of code:
string str = 1 == 1 ? "abc" : "def";
is generated as a simple if / else statement?
Consider the following:
class A
{
}
class B : A
{
}
class C : A
{
}
Now using ternary expression as follows:
A a1 = 1 == 1 ? new B() : new C();
this doesn't even compile with this error:
Type of conditional expression cannot be determined because there is no implicit conversion between 'ConsoleApp1.B' and 'ConsoleApp2.C'
Can anyone shed light on this one?
The type of the conditional operator expression is required to be either the type of the second operand or the type of the third operand. So one of those must be convertible to the other.
In your case, they're not convertible to each other - but both convertible to a third type (A). That isn't considered by the compiler, but you can force it:
A a1 = 1 == 1 ? new B() : (A) new C();
or
A a1 = 1 == 1 ? (A) new B() : new C();
See section 7.14 of the C# 4 spec for more details.
An extract from msdn ?Operator
If condition is true, first expression is evaluated and becomes the result;
if false, the second expression is evaluated and becomes the result.
Only one of two expressions is ever evaluated.
Its pretty explicit.
And your error is pretty explicit too, you are trying to assign a B to a C ... But no cast is available, so error ... Pretty simple
The conditional operator will effectively use the type of the first expression for the second according to whether there is a conversion - and doesn't take into account bases (otherwise it would just always go to object allowing this: ? "hello" : 10).
In this case, the compiler is correct - there is no conversion between the two types. Add a cast, however on the first one - and it'll compile - (A)new B().
Its pretty explicit.
And your error is pretty explicit too, you are trying to assign a B to
a C ... But no cast is available, so error ... Pretty simple
Not relevant at all.
B and C derives from A.
The expression is:
A a1 = 1 == 1 ? new B() : new C();
Both expressions return type that derives from A
Just the compiler looks at the expressions of the ?: operator, and don't care what is the type of variable a1 (left side of the expression)...
The reason for such implementation is very interesting...
Related
I have a valid if: else; code section as follows:
var obj = new Object();
if(Validation.IsDirectory(fileName))
{
obj = Activator.CreateInstance(typeof(FilePath));
}
else
{
obj = Activator.CreateInstance(typeof(FileName));
}
The above generates no error. But, if I translate this to a shorthand if statement, like below:
Validation.IsDirectory(fileName) ? obj = Activator.CreateInstance(typeof(FilePath)) : obj = Activator.CreateInstance(typeof(FileName));
I get the error:
Error CS0201 Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement
In that error documentation it clearly states that:
...Invalid statement is any line or series of lines ending in a
semicolon that does not represent an assignment (=), method call (),
new, -- or ++ operation.
But out of my first 3 statements the first is a method call and the last two are assignment operations.
So, why am I receiving this error? And how do I write this statement as a shorthand if statement?
You cannot make the assignment inside the ternary operator. This operator is used in expressions and yields a value that you then can assign. Also, since now we have a single assignment, we can merge this with the declaration.
(Btw., the initialization expression new Object() in your declaration is superfluous, as the following statements replace this object anyway. object obj; would suffice.)
object obj = Validation.IsDirectory(fileName)
? Activator.CreateInstance(typeof(FilePath))
: Activator.CreateInstance(typeof(FileName));
However, you can simplify this further and call the Activator only once by applying the ternary operator the Type arguments only:
object obj = Activator.CreateInstance(
Validation.IsDirectory(fileName) ? typeof(FilePath) : typeof(FileName)
);
It would be easier to work with FilePath and FileName if they had a common base class that you would use instead of object. The same pattern is used in the System.IO Namespace namespace with the DirectoryInfo Class and the FileInfo Class. Both derive from System.IO.FileSystemInfo and share common members like Name, FullName or Delete().
The ternary conditional operator is not "a shorthand if statement". The ternary operator is an expression which resolves to a value.
In your attempt it would be structurally similar to a line of code which is nothing more than a value:
4;
This line of code is not a statement, hence the error.
You can use the result of the expression as part of a statement. For example:
var x = 4;
Or in your case:
var x = someTernaryExpression;
Which would be:
var obj = Validation.IsDirectory(fileName) ? Activator.CreateInstance(typeof(FilePath)) : Activator.CreateInstance(typeof(FileName));
In short...
When you want to conditionally perform an operation, use if.
When you want to conditionally resolve to a value inline, use ?:.
You perform the assignment as a result of (i.e. after) the ternary operator
obj = Validation.IsDirectory(fileName) ? Activator.CreateInstance(typeof(FilePath)) : Activator.CreateInstance(typeof(FileName));
The ternary operator does the check and returns the first value when true, otherwise it returns the second value.
The returned value can then be assigned normally.
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.
I read this question and I found Tim Schmelter's answer:
By the way, that's one of the differences between the conditional operator and an if-else
you can check the answer for this question, and I can't found the reason ?
if conditional operator work like if-else why if else don't need the cast
question :
int? l = lc.HasValue ? (int)lc.Value : null;
"Tim Schmelter" answer :
You have to cast null as well:
int? l = lc.HasValue ? (int)lc.Value : (int?)null;
By the way, that's one of the differences between the conditional operator and an if-else:
if (lc.HasValue)
l = (int)lc.Value;
else
l = null; // works
The literal null alone does not have a type, but it is implicitly convertible to any reference type and to any Nullable<> type. In the expression:
x = null
where x is assigned to null, the compiler can easily infer from the type of the variable (or field or property or parameter or whatever) x what the null literal shall be converted into. For example if x is of type string, the null shall represent the null reference, while if x is of type int?, the null shall represent an instance of Nullable<int> where HasValue is false.
If x is of type int, no implicit conversion exists, and the above expression shall not compile.
(The declaration var x = null; with var is not legal since null has no type in itself.)
On the other hand, in the expression:
someBoolean ? 42 : null /* illegal */
the compiler cannot figure out what type to convert null into. Remember that int is neither reference type, nor Nullable<> type.
If you meant wrapping into a nullable, use:
someBoolean ? (int?)42 : null
or:
someBoolean ? 42 : (int?)null
In both cases the compiler will automatically see that the other operand (on the other side of the colon :) must also be implicitly converted to int?.
If you meant boxing into some base class or interface of int, write that:
someBoolean ? (object)42 : null
or:
someBoolean ? 42 : (object)null
Now, the expressions above could be sub-expressions of a greater containing expression, but the compiler will still need the type of the ?: expression to be clear by itself. For example in:
int? x;
x = someBoolean ? 42 : null; // still illegal!
even if the sub-expression someBoolean ? 42 : null appears inside a larger expression x = someBoolean ? 42 : null where x does have a type, the sub-expression must still acquire its type "intrinsically". The type of x cannot "leak onto" the sub-expression. This "grammar" seems to be a surprise to many new C# developers. Questions like yours are often seen, see e.g. Nullable type issue with ?: Conditional Operator and the threads linked to it.
The if statement doesn't yield a value, so the statements in the "then" and "else" parts don't need to be type compatible in any way.
The conditional operator yields a value, and therefore both parts must be type compatible in some way in order for the compiler to determine the type of the expression.
MSDN says for the conditional operator :
*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.*
This is the reason. In the if-else constructor the code in else block is independent from the code in if block so the type-casting can be inferred by the compiler in each case.
If I have two yield return methods with the same signature, the compiler does not seem to be recognizing them to be similar.
I have two yield return methods like this:
public static IEnumerable<int> OddNumbers(int N)
{
for (int i = 0; i < N; i++)
if (i % 2 == 1) yield return i;
}
public static IEnumerable<int> EvenNumbers(int N)
{
for (int i = 0; i < N; i++)
if (i % 2 == 0) yield return i;
}
With this, I would expect the following statement to compile fine:
Func<int, IEnumerable<int>> generator = 1 == 0 ? EvenNumbers : OddNumbers; // Does not compile
I get the error message
Type of conditional expression cannot be determined because there is
no implicit conversion between 'method group' and 'method group'
However, an explicit cast works:
Func<int, IEnumerable<int>> newGen = 1 == 0 ? (Func<int, IEnumerable<int>>)EvenNumbers : (Func<int, IEnumerable<int>>)OddNumbers; // Works fine
Am I missing anything or Is this a bug in the C# compiler (I'm using VS2010SP1)?
Note: I have read this and still believe that the first one should've compiled fine.
EDIT: Removed the usage of var in the code snippets as that wasn't what I intended to ask.
No. It is not a bug. It has nothing with yield. The thing is that expression type method group can be converted to delegate type only when it is assigned directly like: SomeDel d = SomeMeth.
C# 3.0 specification:
§6.6 Method group conversions
An implicit conversion (§6.1) exists from a method group (§7.1) to a
compatible delegate type.
This is the only implicit conversion possible with method groups.
How ternary operator is evaluated in terms of types inferences:
A ? B : C:
Make sure that either B or C can be implicitly cast to one another's type. For example A ? 5 : 6.0 will be double because 5 can be implicitly cast to double. Type of A and B in this case is method group and there is no conversion between method group. Only to delegate and it can be enforced as you did.
There are many possible delegate types that could match the signature of the EvenNumbers and OddNumbers methods. For example:
Func<int, IEnumerable<int>>
Func<int, IEnumerable>
Func<int, object>
any number of custom delegate types
The compiler won't try to guess which compatible delegate type you're expecting. You need to be explicit and tell it -- with a cast in your example -- exactly which delegate type you want to use.
Well even
var gen = OddNumbers;
does not work. So you can't expect ternary operator to work.
I guess var can't infer a delegate type.
The yield Return has nothing to do with this.
You are not setting generator to an IEnumerable<int>, you are setting it to a MethodGroup, i.e. a function without the brackets to make the call.
The second statement casts the MethodGroups to Delegates which can be compared.
Perhaps you mean to do somthing like but,
var generator = 1 == 0 ? EvenNumbers(1) : OddNumbers(1);
I couldn't say for sure.
it doesn't have anything to do with iterators, the same code fails to compile if the methods are simple functions. The compiler is reluctant to automatically convert a method to a delegate object, forgetting to use the () in a method call is too common a mistake. You have to do it explicitly.
Rollup of what works and does not:
Does not work:
var generator = 1 == 0 ? EvenNumbers : OddNumbers;
Func<int, IEnumerable<int>> generator = 1 == 0 ? EvenNumbers : OddNumbers;
Does work:
var generator = 1 == 0 ? (Func<int, IEnumerable<int>>)EvenNumbers : OddNumbers;
If it was anything to do with yield or var the latter should also fail.
My guess is a problem with the ternary operator.
The problem is that the statement
var gen = OddNumbers;
Can be interpreted both as
Func<int, IEnumerable<int>> gen = OddNumbers;
and
Expression<Func<int, IEnumerable<int>> gen = OddNumbers;
The compiler can't decide that, so you have to do this.
A method (method group) does not have an intrinsic type, only delegates do. Which is why the ternary operator cannot infer a type to return, and thus is why you have to cast one or the other return value as the type you want to return.
It seems the compiler is not going let this syntax fly.
void main()
{
foo(false?0:"");
}
void foo(int i) {return;}
void foo(string s) {return;}
The only other way I can see of fixing this is something as follows:
void bar(object o)
{
if (o is string){//do this}
else{//im an int, do this}
}
Anyone have any better ideas?
You cannot use a method with a void return type in a ternary expression in this way. End of story.
To understand why this is, remember what the ternary operator actually does -- it evaluates to the following:
(condition ? [true value] : [false value])
What this implies is that the following code:
int x = a ? b : c;
Must be rewritable to:
int x;
if (a)
{
x = b;
}
else
{
x = c;
}
The two above are logically identical.
So how would this work with a method with void as its return type?
// Does this make sense?
int x = condition ? foo(s) : foo(i);
// Or this?
if (condition)
{
x = foo(s);
}
else
{
x = foo(i);
}
Clearly, the above is not legal.
That said, others' suggestions would otherwise be valid if only your foo overloads returned a value.
In other words, if your signatures looked like this:
object foo(string s);
object foo(int i);
Then you could do this (you're throwing away the return value, but at least it'll compile):
object o = condition ? foo(0) : foo("");
Anyway, the ol' if/else is your best bet, in this case.
The method call of foo is determined at compile time, so it cannot call a different method (or overload) based on the result of evaluating the condition. Instead, try something like this:
condition ? foo(0) : foo("")
This way, the compiler will succeed in performing overload resolution and will resolve the first call to foo(int) and the second call to foo(string).
EDIT: As noted by other, you cannot use the ?: operator as a statement, nor can you use methods which return void in it. If your actual methods return compatible types, you could do something like:
int result = condition ? foo(0) : foo("");
If not, you must use an if:
if (condition)
foo(0);
else
foo("");
You're example doesn't make a whole lot of sense (the second example doesn't relate to the first).
I think the first example would be fine as:
void main()
{
foo("");
}
Since 0 will never be passed anyway (false is always false) and you can't use the inline conditional operator without an assignment somewhere (which your example is lacking).
As for the second way, that is probably how I would prefer to see it:
void bar(object o)
{
if(o is string) foo(o as string);
else foo((int)o);
}
I wouldn't pass in an object as a parameter. The int will be boxed, which is a little less efficient. Let the compiler figure out which method to call.
If you wrote:
foo(0);
foo("");
The appropriate method would be called. You could also write:
if (condition) {
foo(0);
} else {
foo("");
}
Depending on what you're trying to do (your example is lacking in a little detail).
If you use Inline if expressions in C#, both parts before and after the ":" have to be of the same type. What you are intending would never work.
Even if you like to do something like this:
DateTime? theTime = true ? DateTime.Now : null;
The compiler is not satisfied. In this case you will have to use:
DateTime? theTime = true ? DateTime.Now : default(DateTime?);
The conditional operator needs the true and false part to be of the same type. Which is why it's not compiling.
var x = condition ? 0 : "";
What type should the compiler choose for x? If you really want it to choose object make a cast or you could force it to choose dynamic in which case method overload would still work but you loose type safety. Both are however strong smells.
Having to test the runtime type is usually a design error but with the limited code (that will always have the same result) it's hard to help with a different approach that would require testing on runtime types
This:
foo(false?0:"")
Could be this:
false ? foo(0) : foo("")
Both results of the conditional operator must of the same type (or be implicitly convertible). So foo(false ? 0 : "") won't work because it is trying to return an Int32 and a String. Here's more information on the conditional operator.
The fix I would do is change that line to false ? foo(0) : foo("").
EDIT: Derp, can't use a conditional operator just in the open like that. They can only be used for assignments. You'll have to use a if/else block. Not in one line, but it'll do in a pinch.