Wednesday, February 20, 2008

.NET structs

.NET has structs and classes. Structs are allocated on the stack instead of the heap and can't participate in inheritance. However, because they're on the stack, they are faster. Check out this article on MSDN for some details, scroll down to the ValueType section. As it says there,
The flexibility afforded by objects comes at a small performance price. Heap-managed objects take more time to allocate, access, and update than stack-managed ones. This is why, for example, a struct in C++ much more efficient than an object. Of course, objects can do things that structs can't, and are far more versatile.

But sometimes you don't need all that flexibility. Sometimes you want something as simple as a struct, and you don't want to pay the performance cost. The CLR provides you with the ability to specify what is called a ValueType, and at compile time this is treated just like a struct. ValueTypes are managed by the stack, and provide you with all the speed of a struct. As expected, they also come with the limited flexibility of structs (there is no inheritance, for example). But for the instances where all you need is a struct, ValueTypes provide an incredible speed boost. More detailed information about ValueTypes, and the rest of the CLR type system, is available on the MSDN Library.

So, it would seem, if you found yourself doing some very C-like tree or list type of coding, you should use a struct instead of a class.

So it would seem. Unfortunately, it turns out this doesn't really work out so well for one very simple reason: structs are Value Types!

Take a look at these two code samples. I'll tell you in advance, the first one fails, the second one passes.
struct Node
{
public int Value;
public string DisplayValue;
}

void Test()
{
List l = new List();
Node n;
n.Value = 5;
n.DisplayValue = "display this";

l.Add( n );

n.DisplayValue = "Display This";

Debug.Assert( n.DisplayValue == l[0].DisplayValue );
}
That one fails.

class Node
{
public int Value;
public string DisplayValue;
}

void Test()
{
List l = new List();
Node n = new Node();
n.Value = 5;
n.DisplayValue = "display this";

l.Add( n );

n.DisplayValue = "Display This";

Debug.Assert( n.DisplayValue == l[0].DisplayValue );
}
That one passes.

The first one fails because "Display This" is not equal to "display this". That is, l[0].DisplayValue returned "display this", not "Display This" as you might have expected.

What went wrong? Its simple. Structs are value types. You passed your struct into the List's Add method as a parameter. Parameters are passed by value. Therefore value types get copied when they are passed as parameters. With reference types the underlying pointer is copied, but the object is not. Thus the first code sample copies the Node and the second code sample doesn't.

update: I had stacks and heaps backwards in the first paragraph... So I flipped them.

What you need is a pointer to the struct. But C# doesn't have pointers (well, not really), so you have to use classes instead. Because of this, I think you'll find that the utility of structs is pretty limited. That probably explains why you never see anybody using them.

2 comments:

  1. Wow. Granted I know VERY little C#, but I wouldn't have guessed that.

    Java doesn't have a "struct" concept. Although it does have primitive values and Objects ... but the primitive values are just your normal simple ones {char, int, double, ...}

    I could see a use for a simple struct like construct, more from the "tuple" side of thing, but thats assuming you could pass it around like other objects.

    Sometimes you do just want to aggregate some data but don't need any of the other object oriented features .... but I don't know what kind of performance you would get in that case.

    Interesting.

    ReplyDelete
  2. As you've said, structs are value types and should only be used when you WANT value type semantics. The example you give is exactly the type of case where you do NOT want value type semantics.

    Microsoft has published some guidance on using structs in which they say only use structs for types that

    * Act like primitive types.
    * Have an instance size under 16 bytes.
    * Are immutable.
    * Value semantics are desirable.

    ReplyDelete