Yes, it was added in version 2.5. The expression syntax is:
a if condition else b
First condition
is evaluated, then exactly one of either a
or b
is evaluated and returned based on the Boolean value of condition
. If condition
evaluates to True
, then a
is evaluated and returned but b
is ignored, or else when b
is evaluated and returned but a
is ignored.
This allows short-circuiting because when condition
is true only a
is evaluated and b
is not evaluated at all, but when condition
is false only b
is evaluated and a
is not evaluated at all.
For example:
>>> 'true' if True else 'false'
'true'
>>> 'true' if False else 'false'
'false'
Note that conditionals are an expression, not a statement. This means you can't use assignment statements or pass
or other statements within a conditional expression:
>>> pass if False else x = 3
File "<stdin>", line 1
pass if False else x = 3
^
SyntaxError: invalid syntax
You can, however, use conditional expressions to assign a variable like so:
x = a if True else b
Think of the conditional expression as switching between two values. It is very useful when you're in a 'one value or another' situation, it but doesn't do much else.
If you need to use statements, you have to use a normal if
statement instead of a conditional expression.
Keep in mind that it's frowned upon by some Pythonistas for several reasons:
- The order of the arguments is different from those of the classic
condition ? a : b
ternary operator from many other languages (such as C, C++, Go, Perl, Ruby, Java, Javascript, etc.), which may lead to bugs when people unfamiliar with Python's "surprising" behaviour use it (they may reverse the argument order).
- Some find it "unwieldy", since it goes contrary to the normal flow of thought (thinking of the condition first and then the effects).
- Stylistic reasons. (Although the 'inline
if
' can be really useful, and make your script more concise, it really does complicate your code)
If you're having trouble remembering the order, then remember that when read aloud, you (almost) say what you mean. For example, x = 4 if b > 8 else 9
is read aloud as x will be 4 if b is greater than 8 otherwise 9
.
Official documentation:
Thanks to everyone who contributed to analyzing this issue. It is clearly a compiler bug. It appears to only happen when there is a lifted conversion involving two nullable types on the left-hand side of the coalescing operator.
I have not yet identified where precisely things go wrong, but at some point during the "nullable lowering" phase of compilation -- after initial analysis but before code generation -- we reduce the expression
result = Foo() ?? y;
from the example above to the moral equivalent of:
A? temp = Foo();
result = temp.HasValue ?
new int?(A.op_implicit(Foo().Value)) :
y;
Clearly that is incorrect; the correct lowering is
result = temp.HasValue ?
new int?(A.op_implicit(temp.Value)) :
y;
My best guess based on my analysis so far is that the nullable optimizer is going off the rails here. We have a nullable optimizer that looks for situations where we know that a particular expression of nullable type cannot possibly be null. Consider the following naive analysis: we might first say that
result = Foo() ?? y;
is the same as
A? temp = Foo();
result = temp.HasValue ?
(int?) temp :
y;
and then we might say that
conversionResult = (int?) temp
is the same as
A? temp2 = temp;
conversionResult = temp2.HasValue ?
new int?(op_Implicit(temp2.Value)) :
(int?) null
But the optimizer can step in and say "whoa, wait a minute, we already checked that temp is not null; there's no need to check it for null a second time just because we are calling a lifted conversion operator". We'd them optimize it away to just
new int?(op_Implicit(temp2.Value))
My guess is that we are somewhere caching the fact that the optimized form of (int?)Foo()
is new int?(op_implicit(Foo().Value))
but that is not actually the optimized form we want; we want the optimized form of Foo()-replaced-with-temporary-and-then-converted.
Many bugs in the C# compiler are a result of bad caching decisions. A word to the wise: every time you cache a fact for use later, you are potentially creating an inconsistency should something relevant change. In this case the relevant thing that has changed post initial analysis is that the call to Foo() should always be realized as a fetch of a temporary.
We did a lot of reorganization of the nullable rewriting pass in C# 3.0. The bug reproduces in C# 3.0 and 4.0 but not in C# 2.0, which means that the bug was probably my bad. Sorry!
I'll get a bug entered into the database and we'll see if we can get this fixed up for a future version of the language. Thanks again everyone for your analysis; it was very helpful!
UPDATE: I rewrote the nullable optimizer from scratch for Roslyn; it now does a better job and avoids these sorts of weird errors. For some thoughts on how the optimizer in Roslyn works, see my series of articles which begins here: https://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/
Best Answer
The compiler is telling you that it doesn't know how convert
null
into aDateTime
.The solution is simple:
Note that
Nullable<DateTime>
can be writtenDateTime?
which will save you a bunch of typing.