views:

126

answers:

3

I read all the questions related to this topic, and they all give reasons why a default constructor on a struct is not available in C#, but I have not yet found anyone who suggests a general course of action when confronted with this situation.

The obvious solution is to simply convert the struct to a class and deal with the consequences.

Are there other options to keep it as a struct?

I ran into this situation with one of our internal commerce API objects. The designer converted it from a class to a struct, and now the default constructor (which was private before) leaves the object in an invalid state.

I thought that if we're going to keep the object as a struct, a mechanism for checking the validity of the state should be introduced (something like an IsValid property). I was met with much resistance, and an explanation of "whoever uses the API should not use the default constructor," a comment which certainly raised my eyebrows. (Note: the object in question is constructed "properly" through static factory methods, and all other constructors are internal.)

Is everyone simply converting their structs to classes in this situation without a second thought?

Edit: I would like to see some suggestions about how to keep this type of object as a struct -- the object in question above is much better suited as a struct than as a class.

+1  A: 

The reason for this is that a struct (an instance of System.ValueType) is treated specially by the CLR: it is initialized with all the fields being 0 (or default). You don't really even need to create one - just declare it. This is why default constructors are required.

You can get around this in two ways:

  1. Create a property like IsValid to indicate if it is a valid struct, like you indicate and
  2. in .Net 2.0 consider using Nullable<T> to allow an uninitialized (null) struct.

Changing the struct to a class can have some very subtle consequences (in terms of memory usage and object identity which come up more in a multithreaded environment), and non-so-subtle but hard to debug NullReferenceExceptions for uninitialized objects.

codekaizen
+4  A: 

For a struct, you design the type so the default constructed instance (fields all zero) is a valid state. You don't [shall not] arbitrarily use struct instead of class without a good reason - there's nothing wrong with using an immutable reference type.

My suggestions:

  • Make sure the reason for using a struct is valid (a [real] profiler revealed significant performance problems resulting from heavy allocation of a very lightweight object).
  • Design the type so the default constructed instance is valid.
  • If the type's design is dictated by native/COM interop constraints, wrap the functionality and don't expose the struct outside the wrapper (private nested type). That way you can easily document and verify proper use of the constrained type requirements.
280Z28
Good answer. Strangely enough, for my particular case above, none of what you said applies, as problems are more easily introduced into the code when the object is a reference type... hence why it was converted to a struct. But in a lot of other cases, using an immutable reference type would be a viable solution.
Jon Seigel
A: 

The reason why there is no possibility to define a default constructor is illustrated by the following expression:

new MyStruct[1000];

You've got 3 options here

  1. calling the default constructor 1000 times, or
  2. creating corrupt data (note that a struct can contain references; if you don't initialize or blank out the reference, you could potentially access arbitrary memory), or
  3. blank the allocated memory out with zeroes (at the byte level).

.NET does the same for both structs and classes: fields and array elements are blanked out with zeroes. This also gets more consistent behavior between structs and classes and no unsafe code. It also allows the .NET framework not to specialize something like new byte[1000].

And that's the default constructor for structs .NET demands and takes care of itself: zero out all bytes.

Now, to handle this, you've got a couple of options:

  • Add an Am-I-Initialized property to the struct (like HasValue on Nullable).
  • Allow the zeroed out struct to be a valid value (like 0 is a valid value for a decimal).
Ruben