10

This seems like a bug to me...

I accept that automatic properties, defined as such:

public decimal? Total { get; set; }

Will be null when they are first accessed. They haven't been initialized, so of course they are null.

But, even after setting their value through +=, this decimal? still remains null. So after:

Total += 8;

Total is still null. How can this be correct? I understand that it's doing a (null + 8), but seems strange that it doesn't pick up that it means it should just be set to 8...

Addendums:

I made the "null + 8" point in my question - but notice that it works with strings. So, it does null + "hello" just fine, and returns "hello". Therefore, behind the scenes, it is initializing the string to a string object with the value of "hello". The behavior should be the same for the other types, IMO. It might be because a string can accept a null as a value, but still, a null string is not an initialized object, correct?

Perhaps it's just because a string isn't a nullable...

Sam Schutte
  • 6,666
  • 6
  • 44
  • 54
  • 7
    "They haven't been initialized, so of course they are null." This is the crux of your confusion. Properties are automatically initially assigned, in this case, to null. You are reasoning from a falsehood: that the property is unassigned. *The property is initially assigned.* There is no such thing as an "unassigned property" in C#. – Eric Lippert Aug 26 '09 at 16:23
  • 1
    Interesting distinction. Also interesting is you could find thousands of examples of people referring to values "initialized to null" as "uninitialized". So perhaps its a common misconception. One thing I am curious about - if we have this issue where we don't want to make "null + 8 = 8" or "null && true == true", why is it that an exception isn't thrown when these types of things are done? Seems like that could possibly prevent some hard to debug errors. – Sam Schutte Aug 26 '09 at 19:09

10 Answers10

31
public decimal? Total { get; set; }

Think of null as "unknown value". If you have an unknown quantity of something and you add 8 more, how many do you have now?

Answer: unknown.

Operations on Nullable Variables

There are cases where operations on unknown values give you knowable results.

public bool? State { get; set; }

The following statements have knowable solutions even though they contain unknown values:

State = null;
nextState = State & false;         // always equals false
nextState = State & true;          // still unknown (null)

nextState = State | true;          // always true
nextState = State | false;         // still unknown (null)

See the pattern?

Of course, if you want Total to be equivalent (equal) to 0 when it is null, you can use the null coalescing operator and write something like this:

Total = (Total ?? 0) + 8;

That will use the value of Total in your equation unless it is null, in which case it will use the value 0.

Robert Cartaino
  • 27,494
  • 6
  • 45
  • 67
7
Null + 8 = Null

You'll need to set it with zero before.

CD..
  • 72,281
  • 25
  • 154
  • 163
  • 1
    Yeah, I know that's the solution, I just think it's dumb. :) – Sam Schutte Aug 26 '09 at 15:04
  • Because it's counter-intuitive, at least to me. If it were my compiler, I would have made the assumption (especially with something like an _automatic_ property) that if you have an uninitialized value and you add something to it, it would just pick up the added value. But, perhaps there are some cases where this kind of assumption would cause problems, who knows. – Sam Schutte Aug 26 '09 at 15:13
  • 2
    You do *NOT* have an uninitialized value. You have a value initialized to null. – Eric Lippert Aug 26 '09 at 16:21
5

null means unknown value,

unknown value + known value = still unknown value
RaYell
  • 69,610
  • 20
  • 126
  • 152
  • I understand what a null is, but it seems to me that it would be a safe assumption that null + 8 would equal 8. But, I guess I'm just wrong. :) – Sam Schutte Aug 26 '09 at 15:06
  • You are wrong indeed :) If `null` was equal to `0` what would be the point of having a `null`? Notice that if you define your property as a non-nullable type (remove the `?` sign) it will be given the default value for decimal type which AFAIK is 0 and you will be able to use your `+= 8` code. – RaYell Aug 26 '09 at 15:11
  • Haha - :) Yeah, I've noticed the default value with auto-props before. I'm not saying that null should be equal to zero, just that if you have an initialized value and you add it to a null, it makes sense to me that the field would just pick up the initialized value. – Sam Schutte Aug 26 '09 at 15:15
  • 1
    Do you really would like it to work like this? Let me give you a real life example why this is wrong: lets assume you have some money in your wallet (however you are not sure how much is that, can be $20, can be $2000, this is our `null`). Now someone gives you extra $500. If this would work as you described your total money should be $500 now. I doubt you would like that :) – RaYell Aug 26 '09 at 15:47
  • I see where you're going, but I'm not sure that your example lines up with what null really is. What I mean is, it's not that null represents some "unknown value", such as "X". If someone told me I have X dollars and I added $500, yes, I would not like ending up with $500. But I don't think null represents "X", it represents (as they would say in VB), Nothing. So it's not the case that there's some unknown memory location that _might_ hold a value - null is very clearly nothing. So in that case, if I have nothing and I added $500, I'm ok with having $500. We're splitting hairs of course. – Sam Schutte Aug 26 '09 at 19:04
3

Here's a one-liner to initialize it on the first call and increment it afterwards:

    public void InitializeOrIncrement(decimal value)
    {
        // if Total is null then initialize, otherwise increment
        Total = (Total == null) ? value : Total + value;
    }

    public decimal? Total { get; set; }
STW
  • 44,917
  • 17
  • 105
  • 161
2

From MSDN:

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.

So, it works as intended.

Francis B.
  • 7,018
  • 2
  • 32
  • 54
1

I know that it makes sense to do

public decimal? Total { get; set; }

Total = (Total ?? 0) + 8;

but wouldnt it just be easier to do :

public decimal Total { get; set; }

initial value of Total is 0

Rowland Shaw
  • 37,700
  • 14
  • 97
  • 166
MarkKGreenway
  • 8,494
  • 5
  • 34
  • 53
  • Yeah, this is actually what I ended up changing my code to. For a running total, it really doesn't make sense for the total to ever by null really, if you think about it. It makes sense for the items being added in to potentially be null, but the total can probably always be >= 0. Interesting discussion though. – Sam Schutte Aug 26 '09 at 19:18
1

As other people have pointed out, null is not equal to zero. Although it may seem more convenient for a null integer to default to zero in the long run it is likely to produce weird results that you may not spot until it is too late.

As a quick example, say one of your data feeds fails and populates your result set with nulls. Your calculations will treat the nulls as zeros and continue to produce results. As numbers are still coming out, even though they are likely wrong, you might never notice that something has gone critically wrong.

Phil Gan
  • 2,813
  • 2
  • 29
  • 38
0

to set the value of the Total is just

Total = 8;

I would recommend reading up on Nullable Types to understand how they work. You can check to see if the property has a value by using HasValue.

From MSDN:

Operators

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 the operands are null; otherwise, the operator uses the contained value to calculate the result. For example:

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

a++;         // Increment by 1, now a is 11.
a = a * 10;  // Multiply by 10, now a is 110.
a = a + b;   // Add b, now a is null.
Russ Cam
  • 124,184
  • 33
  • 204
  • 266
  • Thanks. I used nullable types quite a bit, and have run into this issue with automatic property initialization quite a bit, but never bothered to ask why. I suppose it makes sense, though I don't know if I would have done it that way... – Sam Schutte Aug 26 '09 at 15:10
  • There is a good section on Nullable Types in C# in Depth, amongst other interesting details of mainly C#3, but also a little C#1 and a bit more on C#2. A great book indeed. – Russ Cam Aug 26 '09 at 15:16
  • The whole idea of nullable types is that you can assign null values to value types :) – Russ Cam Aug 26 '09 at 16:04
0

Null isn't the same as zero. Zero plus eight is eight... but null plus eight? Always null. Just like infinity plus anything is still infinity - it's undefined.

You'll find that this is universally true of null. Every database (at least that I've ever worked with) will give you the same result.

womp
  • 115,835
  • 26
  • 236
  • 269
0

public decimal? Total { get; set; }

Would something like this work? Some sort of automatic initalization if the value isn't yet set.

public decimal? Total
{
  get { return this.totalValue;}
  set
  { 
     if(totalValue == null) { this.totalValue = 0;}
     this.totalValue = value;
  }
}

private decimal? totalValue;
Ian
  • 33,605
  • 26
  • 118
  • 198
  • ...but if you're not using auto-implemented properties then why not just initialize your backing field as part of it's declaration? `private decimal? totalValue = 0;` – STW Aug 26 '09 at 15:08
  • Yeah, let's face it, the only reason I'm using automatic properties at all is to be lazy about typing. (Not object typing - just finger typing!) If I were going to split it out, I would just default it to 0. – Sam Schutte Aug 26 '09 at 15:18
  • Yooder, because he might not want the value to be 0, he may wish it to be null... Robert, no actually if someone sets Total to null, it will be set to 0, and then reset to null in this case, as 'value' would be null. 0 is just used as a lazy initalizer. – Ian Aug 27 '09 at 08:11