This is pretty common and straightforward (and quite elementary), but can be a gotcha if you're new to software development so I figured it'd be worth blogging about.
When you're assembling your applications you'll be invariably presented with the quandary of whether you should use a struct or a class to represent your data. This choice isn't often cut and dry. While in many respects they are semantically similar, there are some distinctions that should be known.
First and foremost, perhaps, is how they are allocated in memory. Structs, by nature, are ValueType objects and are loaded onto the application's stack. Class instances, on the other hand reference types and are heap-allocated. A nicety of stack-allocated objects is that they are automatically taken care of when a procedure finishes (unless you move the object to the heap). They are more deterministic in that respect. Memory, however, is finite, and the stack is much smaller than the heap so you're limited to how many objects you can load onto it. Stack allocations are faster than heap allocations as well, though the .NET runtime has some advanced memory assignment and allocation algorithms that make heap allocations nearly (almost imperceptively) as fast as stack allocations. Okay, enough about that.
There are some other marked differences between the two:
Constructors: In a struct you can define constructors, but the constructors be parameterized; you cannot create a parameterless constructor as you can with a class.
Fields: Within a struct you can define fields, but initialization of the field's value in the declaration is not acceptable. An exception to this is that static fields can be initialized.
As a parameter: Another important distinction between the two is their behavior when passed as a parameter to a method. When you pass a struct, it is passed by value (that is, it's copied) whereas a class instance is passed by reference (the reference is copied - not the object). This has some far-reaching implications. Suppose you have the following scenario, ignoring the fact that a similar struct may already exist in the .NET framework (it's just for illustration purposes):
struct Coords {
public int x;
public int y;
public Coords(int xCoord, int yCoord) {
x = xCoord;
y = yCoord;
}
}
public static void Main() {
Coords pt = new Coords(10, 10);
movePt(pt, 5, 3);
// what is the value of pt.x and pt.y here?
}
private static movePt(Coords pt, int xOffset, int yOffset) {
pt.x += xOffset;
pt.y += yOffset;
}
When you call movePt() pt is copied so the method is affecting a different pt than the one passed in (though the values are the same). Now, this may be exactly the functionality you want, but then again it might not. Sure you can solve your problem by making the parameter a ref parameter, but that's another story, and it might impose some discipline upon the caller.
Interfaces: Ok, now this is potentially a bit more confusing for the novice, but here goes. Structs, just like classes, can implement interfaces. This adds a twist to the ValueType / reference type relationship. When you cast a struct to its interface, the struct is boxed; it is copied up to the managed heap. Changes made to it through the interface affect the boxed copy. The struct is never unboxed back to the struct (unless you explicitly unbox it). Let's see this in action (Okay, okay, I know the names are stupid, but hey, it's all off the cuff):
interface IIncrementer {
void AddValue(int value);
}
struct IntWrapper : IIncrementer {
private int _value;
public IntWrapper(int value) {
_value = value;
}
void IIncrementer.AddValue(int value) {
_value += value;
}
public int Value {
get { return _value; }
}
}
public sealed class EntryPoint {
public static void Main() {
IntWrapper iw = new IntWrapper(5); // the initial value is 5
((IIncrementer)iw).AddValue(3); // the value of iw is still 5! the
// change to the value happened on the
// heap copy and was immediately
// discarded!
// box and unbox example:
IIncrementer iiw = (IIncrementer)iw; // box it and obtain a reference
iiw.AddValue(4); // increment the boxed copy
iw = (IntWrapper)iiw; // unbox it, replacing the stack copy.
// the value is now 9!
}
}
Personally, I find the class model (vs the struct model) much more intuitive and welcoming, but structs definitely have their place - I use them all the time. When it comes down to it, though, I think that one of the interesting characteristics of structs is that they have meaning or state in their default, unitialized form. That is, the default state of a struct must have some intuitiveness upon creation (you can't control it's default constructor, nor can you deny someone from creating an instance without parameters). You don't want someone creating an instance of your struct and calling a method or referencing a field/property and having it throw. This is where classes and their constructors come in really handy.
Pretty much everything mentioned above focuses on structs that have ValueType members. The playing field changes a little if you a struct that has reference types (such as Strings or other objects) internally as those are heap allocated objects maintained within a ValueType - now your struct isn't quite as deterministic.
Enjoy!