C# Structs vs Classes – Why They Are Separate Concepts

\clrchistorylanguage-design

While programming in C#, I stumbled upon a strange language design decision that I just can't understand.

So, C# (and the CLR) has two aggregate data types: struct (value-type, stored on the stack, no inheritance) and class (reference-type, stored on the heap, has inheritance).

This setup sounds nice at first, but then you stumble upon a method taking a parameter of an aggregate type, and to figure out if it is actually of a value type or of a reference type, you have to find its type's declaration. It can get really confusing at times.

The generally accepted solution to the problem seems to be declaring all structs as "immutable" (setting their fields to readonly) to prevent possible mistakes, limiting structs' usefulness.

C++, for example, employs a much more usable model: it allows you to create an object instance either on the stack or on the heap and pass it by value or by reference (or by pointer). I keep hearing that C# was inspired by C++, and I just can't understand why didn't it take on this one technique. Combining class and struct into one construct with two different allocation options (heap and stack) and passing them around as values or (explicitly) as references via the ref and out keywords seems like a nice thing.

The question is, why did class and struct become separate concepts in C# and the CLR instead of one aggregate type with two allocation options?

Best Answer

The reason C# (and Java and essentially every other OO language developed after C++) did not copy C++'s model in this aspect is because the way C++ does it is a horrendous mess.

You correctly identified the relevant points above: struct: value type, no inheritance. class: reference type, has inheritance. Inheritance and value types (or more specifically, polymorphism and pass-by-value) don't mix; if you pass an object of type Derived to a method argument of type Base, and then call a virtual method on it, the only way to get proper behavior is to ensure that what got passed was a reference.

Between that and all the other messes that you run into in C++ by having inheritable objects as value types (copy constructors and object slicing come to mind!) the best solution is to Just Say No.

Good language design isn't just implementing features, it's also knowing what features not to implement, and one of the best ways to do this is by learning from the mistakes of those who came before you.

Related Topic