Question about expression: a ? b : c - c#

I tried to use the following example codes by using a ? b : c expression:
DateTime? GetValue(string input)
{
DateTime? val = string.IsNullOrEmpty(input) ? null : DateTime.Parse(input);
return val;
}
I got compiling error since in the a ? b : c expression because b and c are different data types; Not sure if I can use (DateTime?) case to c part?
DateTime? val = string.IsNullOrEmpty(input) ? null : (DateTime?) DateTime.Parse(input);
I would rather not use if to split this one into two or three statement.

return string.IsNullOrEmpty(input) ? (DateTime?)null : DateTime.Parse(input);
//or
return string.IsNullOrEmpty(input) ? null : (DateTime?)DateTime.Parse(input);
Either works, you have to provide some means of compatability between the two types, since DateTime cannot be null, you need to explicitly with one that you're trying to go to DateTime?, then the compiler can implicit cast the other.

The compiler is ensuring that b and c in your a ? b: c are of the same type. In your original example c is a DateTime (since DateTime.Parse returns a DateTime), and b can not be a DateTime cause its null, so the compiler says:
Type of conditional expression cannot
be determined because there is no
implicit conversion between ''
and 'System.DateTime'
You can get it to work (Because there is an implicit convertion from DateTime? to DateTime)
DateTime? val = string.IsNullOrEmpty(input) ? (DateTime?)null : DateTime.Parse(input);
But ... I think this is one of those cases where the following is much easier to follow.
DateTime? val = null;
if (!string.IsNullOrEmpty(input)) {
val = DateTime.Parse(input);
}
With the caveat that the whole premise of the function is pretty risky, you are only failing early sometimes.
The method has very odd semantics! It will fail with an exception if an invalid date format is passed in unless it is null or an empty string. This violates the fail early principle.

Did you actually try it? Yes it works. Go grab LINQPad to try little things like this.
LINQPad is more than just a LINQ tool: it's a highly ergonomic code snippet IDE that instantly executes any C#/VB expression, statement block or program – the ultimate in dynamic development. Put an end to those hundreds of Visual Studio Console projects cluttering your source folder!

I just tried
public static DateTime? GetValue(string input)
{
DateTime? val = string.IsNullOrEmpty(input) ? null : (DateTime?)DateTime.Parse(input);
return val;
}
and it worked fine.

Use this:
DateTime? val = string.IsNullOrEmpty(input) ? null : new DateTime?(DateTime.Parse(input));
Edit:
The other answers will work too, but with this syntax you won't even need casting.

Related

Can you use a ternary operator in a C# local function?

Here is what I would like to implement:
public static void foo()
{
// Local function
int? parseAppliedAmountTernary( decimal? d ) {
d.HasValue ? return Convert.ToInt32( Math.Round(d.Value, 0) ) : return null;
}
// The rest of foo...
}
However, I get a compilation error. Is this syntax even possible? I am using Visual Studio 2019, .NET Framework 4, which (currently) equates to C# 7.3.
Note - I am just trying to figure out the syntax... any philosophical discussion regarding the "readability" of the code or other aesthetics, while interesting, are beside the point. :)
Code sample (uses Roslyn 4.0)
https://dotnetfiddle.net/TlDY9c
The ternary operator is not a condition but an expression which evaluates to a single value. It is this value that you have to return:
return d.HasValue ? (int?)Convert.ToInt32(Math.Round(d.Value, 0)) : null;
Note, that before C# 9.0 both the value on the left and the the value on the right of the colon have to be of the same type; therefore, you need to cast the non null value here. Starting with C# 9.0 the type can be infered from the target type.
You should be able to write it like this:
int? parseAppliedAmount(decimal? d) => d.HasValue ? Convert.ToInt32(Math.Round(d.Value, 0)) : null;

Continue as First Statement in Concise If Else

I have the following if else statement inside of a foreach loop:
string date = GetDate(item)
if (date == null)
{
continue;
}
else
{
article.Date = date;
}
And I would like to write this in the concise format using ? ::
string date = GetDate(item)
date == null ? continue : article.Date = date;
From what I gather, this should work, as it is in the format condition ? first_expression : second_expression;, where first_expression is continue, however in Visual Studio 2015 I am seeing these errors for the given areas:
continue
Invalid expression term 'continue'
Syntax error, ':' expected
Invalid expression term 'continue'
; expected
:
; expected
: expected
Is it possible to use continue in this types of If/Else? If not, is there any reason for this?
https://msdn.microsoft.com/en-gb/library/ms173144.aspx
An expression is a sequence of one or more operands and zero or more operators that can be evaluated to a single value, object, method, or namespace
continue is not an expression
Your code is trying to assign continue to to your "date" variable. This doesn't make sense. I'm afraid there's no way to use the ternary operator for what you're trying to accomplish.
Well,
string date = GetDate(item)
date == null ? continue : article.Date = date;
Conditional ?: operator must return something, you can read it as:
// if(smth) { return smth} else { return smthElse; }
var result = a ? b : c;
Obviously you cannot return continue because it is not a value.
What you can do is to assign the same value if returned result is null and check it using null coalescing operator. Assuming that there are no further operations in loop this code could be refactored to something like :
article.Date = GetDate(item) ?? article.Date;
Try this one, With the default you can add default time 1/1/0001 12:00:00 AM value to your variable date if the date is null,
date == null ? default(DateTime): article.Date = date;

Nullable Object must have a value #2

I'm trying to reuse the same code I've always used but now it is encountering an error.
I'm looping through various user tables, and in there I do this:
DateTime dcdt = (DateTime)u.DateCreated;
DateTime lldt = (DateTime)u.LastLogon;
userRow["DateCreated"] = dcdt.ToShortDateString();
inside the loop. I get the error:
System.InvalidOperationException: Nullable object must have a value.
The error highlights "lldt" line, instead of "dcdt" which comes first. That is strange in and of itself. Both these fields in the database "allow nulls" is checked. And they both could be null or neither might be null.
The two values are both listed as DateTime? types through intellisense.
I don't understand why ASP.NET refuses to allow me to output blank for null dates. If it is empty/null, then logic would suggest that ASP.NET should just print nothing.
How else am I suppose to output null dates? I tried adding if statements to prevent trying to cast null DateTimes, but it doesn't help, it makes no sense.
As you've said, the data type of u.LastLogon is DateTime?. This means that it may or may not have a value. By casting to DateTime, you are requiring it to have a value. In this case, it does not.
Depending on what you're trying to do with it, you may want to check the HasValue property:
userRow["LastLogon"] = u.LastLogin.HasValue ?
(object) u.LastLogin.ToShortDateString() : DBNull.Value;
If your database LastLogon column is of DateTime type, then you should be able to do:
userRow["LastLogon"] = u.LastLogin.HasValue ?
(object) u.LastLogin.Value : DBNull.Value;
You need to do something like the following in your data access code:
DataTable dt = ExecuteSomeQuery() ;
object value = dt.Rows[0]["nullable_datetime_column"] ;
DateTime? instance = value != null && value is DateTime ? (DateTime?)value : (DateTime?)null ) ;
If the column returned is NULL, it will be returned as a System.DBNull, otherwise it will be returned as an instance of DateTime (or whatever the appropriate mapped type is — int, string, etc). Consequently, you need to check the type of object returned from the query before trying to cast it.
Looks like you are trying to call a method (dcdt.ToShortDateString()) on a DateTime? which doesn't have a value (it is, indeed, null). Try this:
dcdt.HasValue ? dcdt.ToShortDateString() : String.Empty;
EDIT (Just re-read the question): Also, don't try to convert to DateTime. Preserve the nullable.
EDIT #2 (based on comments):
Try this:
if (dcdt.HasValue)
{ userRow["DateCreated"] = dcdt.ToShortDateString(); }
else
{ userRow = DbNull.Value }
I saw that Dexter asked how he should go about it. Well, I would create an extension.
static class DateTimeExtensions
{
public static string ToString(this DateTime? dateTime, string format)
{
return dateTime.HasValue ? dateTime.Value.ToString(format) : String.Empty;
}
}
And then you can do:
DateTime? dt = null;
DateTime? dt2 = DateTime.Now;
Console.WriteLine(dt.ToString("dd-MM-yy"));
Console.WriteLine(dt2.ToString("dd-MM-yy"));
Note that I can call extension method on a nullable type if the object is null.
The problem is .NET null is not the same as SQL NULL. SQL Null is System.DBNull. So it is a [non-null] value in .NET.
Short answer
DateTime? dateTime = u.LastLogon?.ToShortDateString()

Is it right to cast null to nullable when using ternary expression assigning to a nullable type?

It feels strange to me to be casting null to a type so I wanted to double check that this is the right way to do this:
decimal? d = data.isSpecified ? data.Value : (decimal?)null;
NOTE: I am marking the answer that suggests the method that I personally like the best:
decimal? d = data.isSpecified ? data.Value : default(decimal?)
Yes, that's fine. Alternatives:
condition ? (decimal?) value : null
condition ? new decimal?(value) : null
condition ? value : default(decimal?)
condition ? value : new decimal?()
Pick whichever you find most readable.
There's nothing you can do outside the expression itself, as it's the type of the expression which the compiler doesn't know. For example, putting the whole expression in brackets and casting it wouldn't help.
Yes, it is perfectly fine.
Is data.Value of type decimal? If so, here's an alternative notation, without the cast:
decimal? d = data.isSpecified ? new decimal?(data.Value) : null;

Type result with conditional operator in C#

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.

Categories

Resources