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 struct
s as "immutable" (setting their fields to readonly
) to prevent possible mistakes, limiting struct
s' 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 typeDerived
to a method argument of typeBase
, 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.