Matthias Müller
Matthias Müller

Reputation: 3483

? Constructor handling with nullable types

small question, just for understanding: I have 2 nullable datetimes. I read out the create time and the update time, both can be filled. So I want to check which is later:

lastChangedIndrole = (tmpCreate > tmpUpdate ? tmpCreate : tmpUpdate);

But here happen some strange things. I would expect to throw a error when f.e. tmpUpdate is null, but it seem to return something, but not the corret value but just the second, in my example the update.

Is there anything I dont understand? I'd think the code checks the miliseconds to 1900 and if there is a null value an error gets thrown. But this doesnt happen. Is that some magic I dont understand?

P.S. : Is there a special word for the ? constructor like IIF in vb? It's hard to search something.

Thanks and a good start in the week

Matthias

Upvotes: 4

Views: 4903

Answers (6)

faester
faester

Reputation: 15086

Use ^ (xor) to check if exactly one condition is true ( equals null) then ?? to return the first not-null value. If both are not null use your existing expression.

if (tmpCreate == null ^ tmpUpdate == null) lastChangedIndrole = tmpCreate ?? tmpUpdate; else lastChangedIndrole = (tmpCreate > tmpUpdate ? tmpCreate : tmpUpdate);

Upvotes: 1

faester
faester

Reputation: 15086

Use ^ (xor) to check if exactly one condition is true (equals null) then ?? to return the first not-null value. If both are not null use your existing expression.

if (tmpCreate == null ^ tmpUpdate == null)
   lastChangedIndrole = tmpCreate ?? tmpUpdate;
else
   lastChangedIndrole = (tmpCreate > tmpUpdate ? tmpCreate : tmpUpdate); 

But you could also choose to assign the first non-null value directly and then overwrite it if tmpUpdate is larger than the value:

lastChangedInRole = tmpCreate ?? tmpUpdate; 
if (tmpUpdate > lastChangedInRole) 
  lastChangedInRole = tmpUpdate;

(Rationale: If only one has a value, the comparison will always be false and the non null value will be assigned using ??, otherwise the tmpCreated will be assigned and it is only neccessary to compare it to tmpUpdate.)

Upvotes: 1

Lee
Lee

Reputation: 144176

C# lifts the < and > operators over nullable types, and they return false if one of the arguments is null.

Therefore tmpCreate > tmpUpdate evaluates to false if tmpCreate or tmpUpdate are null.

It is described in the specification:

7.3.7 Lifted operators

• For the relational 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 produces the value false if one or both operands are null. Otherwise, the lifted operator unwraps the operands and applies the underlying operator to produce the bool result.

Upvotes: 3

Sriram Sakthivel
Sriram Sakthivel

Reputation: 73492

Nullable types being null throws exception when Nullable.Value read. But it won't throw exception when compparing it will give unexpected results though(Comparing null with non-null value never returns true).

Following snippet will illustrate the problem

DateTime? dt1 = null;
DateTime? dt2 = DateTime.Now;

bool b1 = dt2 > dt1;//false(we expect true here)
bool b2 = dt2 < dt1;//false
bool b3 = dt2 == dt1;//false

This behavior is documented Here

When you perform comparisons with nullable types, if the value of one of the nullable types is null and the other is not, all comparisons evaluate to false except for != (not equal). It is important not to assume that because a particular comparison returns false, the opposite case returns true. In the following example, 10 is not greater than, less than, nor equal to null. Only num1 != num2 evaluates to true.

Upvotes: 3

Lasse V. Karlsen
Lasse V. Karlsen

Reputation: 391456

When you compare nullable values, either to other nullable values, or to non-nullable values, operators on the non-nullable types are "lifted", and thus also apply to their nullable counterparts.

However, special handling will handle the case where either or both are null.

Here's a LINQPad example:

void Main()
{
    int? a = 10;
    int? b = null;

    (a > b).Dump();
    (b > a).Dump();
    (a == b).Dump();
    (a != b).Dump();
}

Output:

False
False
False
True

As you can see, when comparing two nullable ints, where one is null, only the equality operators produces the expected result.

If we make the a variable a non-nullable int:

int a = 10;

but otherwise keep the code, then it produces the exact same results.

What if both are null?

int? a = null;
int? b = null;

Produces:

False
False
True
False

Conclusion:

  • Equality operators (== and !=) correctly handle nulls with nullable types
  • Less than or greater than operators does not, they will return false if one of the operands is null, even if you switch the comparison around. Basically, 10 is neither less than or greater than null.

If you try to read the .Value property of a null nullable type value, it will throw an exception, but the operators do not go directly for the .Value property, but check the .HasValue property first, and then handle these cases before attempting the actual comparison.

Upvotes: 1

SWeko
SWeko

Reputation: 30902

You can compare two DateTime? objects, but most of the time, when at least one of the operands is null, the result will be false.

For example:

DateTime? today = DateTime.Today;
DateTime? yesterday = DateTime.Today.AddDays(-1);
DateTime? nodate = null;
DateTime? nodate2 = null;

Console.WriteLine(today > yesterday); //true
Console.WriteLine(today < yesterday); //false

Console.WriteLine(today > nodate); //false
Console.WriteLine(today == nodate); //false
Console.WriteLine(today < nodate); //false

Console.WriteLine(nodate > yesterday); //false
Console.WriteLine(nodate == yesterday); //false
Console.WriteLine(nodate < yesterday); //false

Console.WriteLine(nodate > nodate2); //false
Console.WriteLine(nodate == nodate2); //true - this is the exception
Console.WriteLine(nodate < nodate2); //false

I would recommend avoiding being too clever, and being more explicit in the code:

if (tmpUpdate.HasValue)
{
   if (tmpCreate.HasValue)
   {
       lastChangedIndrole = (tmpCreate > tmpUpdate ? tmpCreate : tmpUpdate);
   }
   else
   {
       lastChangedIndrole = tmpUpdate;
   }
}
else
{
   if (tmpCreate.HasValue)
   {
       lastChangedIndrole = tmpCreate;
   }
   else
   {
       lastChangedIndrole = null;
   }
}

Upvotes: 2

Related Questions