tags:

views:

212

answers:

5

I've seen different questions on SO about not being able to use parameterless constructors or not setting field initializers, but I think my question kind of goes a step beyond that.

What I would like to know is, how would I go about setting up the "default" value of a struct I need to define? Say I'm making a TaxID struct, and the default value needs to be 999-99-9999 for whatever reason. This is already done with other structs in .NET, namely the DateTime. Any time you declare a DateTime the value is immediately set to Jan 1 0001 12:00 AM. Why does it get set to this value and not 0/0/0000 00:00:0000 AM? There must be something going on behind the scenes that makes the value actually make "sense" at it's "default", even given the restrictions put on us by c# with regards to structs.

A: 

You cannot declare a default constructor in a C# struct. You can simulate this behavior by creating a private field and writing a property to return the default value if it's 1/1/1

Mehrdad Afshari
I'm aware that you can't declare a default constructor for a struct in C#, but I'm also aware that you can't use field initializers in a struct as well. So the problem is how do you achieve having a "default" value if you can't set it before hand? Maybe I'm missing something.
Joseph
+3  A: 

Any time you declare a DateTime the value is immediately set to Jan 1 0001 12:00 AM. Why does it get set to this value and not 0/0/0000 00:00:0000 AM? There must be something going on behind the scenes that makes the value actually make "sense" at it's "default", even given the restrictions put on us by c# with regards to structs.

More likely it implicitly initializes a ticks field to default(int) = 0, and 0 ticks means DateTime.MinValue when you go to get the value.

mquander
I see, so basically the DateTime struct acts as kind of a wrapper behind a long or int or something that is defaulted to 0, but the DateTime represents that as 1/1/0001 12:00 AM when queried for it's Value?
Joseph
@joseph: that is correct, it has a internal Int64 Ticks which, like EVERY struct, defaults to 0
Lucas
+6  A: 

In general, this should be avoided. If you need to require a good, default case which would require construction, you should consider changing to a class.

One of the core design guidelines for structs is: Do ensure that a state where all instance data is set to zero, false, or null (as appropriate) is valid.

For details, see the design guidelines.


I just looked this section up in the design guidelines 2nd edition, and they have an example in detail there using properties and non-conventional overrides to work around this, as well. The basic concept was to save the value privately in a way that 0 is the "good default", and do some form of transform in every property and method override (including ToString). In their case, they used a positive integer as an example, and always save curVal-1 in the private member, so the default value (0) is treated like a value of 1. They added a constructor with an int val, and save value-1 internally, etc.

This seems like a lot of hidden, unexpected overhead, though - so I'd personally still recommend (and use) a class in this case.

--- Edit in response to comments ---

DateTime, as your example, is the way it is because 0 == 1/1/0001 at Midnight. DateTime uses a single, ulong to represent ticks from 1/1/0001. That is why (from here):

"The DateTime value type represents dates and times with values ranging from 12:00:00 midnight, January 1, 0001 Anno Domini (Common Era) through 11:59:59 P.M., December 31, 9999 A.D. (C.E.) "

This is the full range of ulong in ticks. A "0" in the struct of DateTime is treated as 1/1/0001 when you convert to a string - the values aren't 1 + 1 + .... - it's a single 0.

Reed Copsey
I understand the design consideration one should make when determining between a class and a struct, but that doesn't mean that I should be forced into having zeros in all my structs. The DateTime, again, is a good example of this. I'm just wondering how they achieved that "default"
Joseph
See my edit - DateTime of zeros is that default (1/1/0001 at midnight). They are only editing the way they convert 0 to a string in ToString()
Reed Copsey
So the only way to achieve a "default" is going to be to "wrap" some functionality around some other value type's default value. In this case the ulong being defaulted to 0 ends up being represented as 1/1/0001. I would then to do something similar by interpreting 0 to mean 999-99-9999.
Joseph
Exactly. There is no way to actually force a default value. This is because of how arrays of structs are constructed - there's no constructor, it's just a big block of zeroed out memory, so there's no way to force a default. You have to simulate it in other code.
Reed Copsey
+2  A: 

Nothing sinister is going on. The internal representation of a DateTime is the number of 100 nanosecond "ticks" since 12:00:00 midnight, January 1, 0001 Anno Domini. When the DateTime is created, the tick count is zero. If you look at DateTime with Ildasm, you will see it's a bit more complicated than that, but you get the idea. :)

JP Alioto
A: 

Just for completeness:

Value types in C# (including arrays, structs, enums) are always initialized to all zeros. Reference types are also initially zero, better know as null.

In case of enums, zero is the default value, and is always a possible value even if it's not a defined in the enum. Personally, I usually define 0 as None, Empty, Unknown or something similar:

enum MyEnum { A = 1, B = 2, C = 3 };
// ...
MyEnum value = new MyEnum(); // value has a nameless 0

In case of arrays, if it's an array of reference types, they are all initially null (which you can think of as a pointer to address 0). If it's an array of value types, they are all initially zero or the equivalent (see other answers about DateTime.Ticks and why a "zeroed" DateTime is equal to 1/1/0001 12:00am).

Coming back to structs: the same applies. They will always have a default parameterless constructor and you cannot avoid this. All value type members will initially be zero (or equivalent) and all reference type members will be null. Think about int, short, decimal, char, DateTime (ahem), TimeSpan, Size, Point, Rectangle, Color, Guid... they are all initialized to all zeros.

Lucas