145

I know that structs in .NET do not support inheritance, but its not exactly clear why they are limited in this way.

What technical reason prevents structs from inheriting from other structs?

Carl Manaster
  • 39,912
  • 17
  • 102
  • 155
Juliet
  • 80,494
  • 45
  • 196
  • 228
  • 7
    I'm not dying for this functionality, but I can think of a few cases when struct inheritance would be useful: you might want to extent a Point2D struct to a Point3D struct with inheritance, you might want to inherit from Int32 to constrain it values between 1 and 100, you might want to create a type-def which is visible across multiple files (the Using typeA = typeB trick has file scope only), etc. – Juliet Aug 03 '09 at 15:25
  • 4
    You might want to read http://stackoverflow.com/questions/1082311/why-should-a-struct-be-less-than-16-bytes, which explains a bit more about structs and why they should be restricted to a certain size. If you want to use inheritance in a struct then you should probably be using a class. – Justin Aug 03 '09 at 15:35
  • 1
    And you might want to read http://stackoverflow.com/questions/1222935/why-dont-structs-support-inheritance/1223227#1223227 as it goes in depth why it just couldn't be done in the dotNet platform. They cold have made it the C++ way, with the same problems which can be disastrous for a managed platform. – Dykam Aug 03 '09 at 16:23
  • @Justin Classes have performance costs that structs can avoid. And in game development that really matters. So in some cases you shouldn't be using a class if you can help it. – Gavin Williams Oct 27 '15 at 04:16
  • 1
    @Dykam I think it can be done in C#. Disastrous is an exaggeration. I can write disastrous code today in C# when i'm not familiar with a technique. So that's not really an issue. If struct inheritance can solve some problems and give better performance under certain scenarios, then I'm all for it. – Gavin Williams Oct 27 '15 at 04:16
  • @GavinWilliams, unless you actually have an idea of how to do it... It's basically impossible because of the point(s) raised in the answer. The disastrous part refers to the fact that you start breaking the memory model. Contrary to what you say, unless you work with the unsafe/Marshall (and related) API's and features, you cannot break the memory model of C#, this would allow that in a very easy way. – Dykam Oct 29 '15 at 10:20

10 Answers10

135

The reason value types can't support inheritance is because of arrays.

The problem is that, for performance and GC reasons, arrays of value types are stored "inline". For example, given new FooType[10] {...}, if FooType is a reference type, 11 objects will be created on the managed heap (one for the array, and 10 for each type instance). If FooType is instead a value type, only one instance will be created on the managed heap -- for the array itself (as each array value will be stored "inline" with the array).

Now, suppose we had inheritance with value types. When combined with the above "inline storage" behavior of arrays, Bad Things happen, as can be seen in C++.

Consider this pseudo-C# code:

struct Base
{
    public int A;
}

struct Derived : Base
{
    public int B;
}

void Square(Base[] values)
{
  for (int i = 0; i < values.Length; ++i)
      values [i].A *= 2;
}

Derived[] v = new Derived[2];
Square (v);

By normal conversion rules, a Derived[] is convertible to a Base[] (for better or worse), so if you s/struct/class/g for the above example, it'll compile and run as expected, with no problems. But if Base and Derived are value types, and arrays store values inline, then we have a problem.

We have a problem because Square() doesn't know anything about Derived, it'll use only pointer arithmetic to access each element of the array, incrementing by a constant amount (sizeof(A)). The assembly would be vaguely like:

for (int i = 0; i < values.Length; ++i)
{
    A* value = (A*) (((char*) values) + i * sizeof(A));
    value->A *= 2;
}

(Yes, that's abominable assembly, but the point is that we'll increment through the array at known compile-time constants, without any knowledge that a derived type is being used.)

So, if this actually happened, we'd have memory corruption issues. Specifically, within Square(), values[1].A*=2 would actually be modifying values[0].B!

Try to debug THAT!

nevermind
  • 2,300
  • 1
  • 20
  • 36
jonp
  • 13,512
  • 5
  • 45
  • 60
  • 13
    The sensible solution to that problem would be to disallow the cast form Base[] to Detived[]. Just like casting from short[] to int[] is forbidden, although casting from short to int is possible. – Niki Aug 03 '09 at 16:34
  • 3
    +answer: the problem with inheritance didn't click with me until you put it in terms of arrays. Another user stated that this problem could be mitigated by "slicing" structs to the appropriate size, but I see slicing as being the cause of more problems than it solves. – Juliet Aug 03 '09 at 16:50
  • 4
    Yes, but that "makes sense" because array conversions are for implicit conversions, not explicit conversions. short to int is possible, but requires a cast, so it's sensible that short[] can't be converted to int[] (short of conversion code, like 'a.Select(x => (int) x).ToArray()'). If the runtime disallowed the cast from Base to Derived, it would be a "wart", as that IS allowed for reference types. So we have two different "warts" possible -- forbid struct inheritance or forbid conversions of arrays-of-derived to arrays-of-base. – jonp Aug 03 '09 at 16:53
  • 3
    At least by preventing struct inheritance we have a separate keyword and can more easily say "structs are special", as opposed to having a "random" limitation in something that works for one set of things (classes) but not for another (structs). I imagine that the struct limitation is far easier to explain ("they're different!"). – jonp Aug 03 '09 at 16:55
  • 1
    @jonp: short to int doesn't require a cast. It's an implicit conversion. – Niki Aug 03 '09 at 17:19
  • 2
    You're right, that is terrible assembly. In fact, I think you'd need a C compiler to compile it ;) – Richard Szalay Aug 03 '09 at 17:21
  • 2
    need to change the name of the function from 'square' to 'double' – John Jan 14 '10 at 22:05
  • 1
    Everything that you've mentioned here in the context of arrays would apply equally to a struct on the stack; arrays are just a single example of why this can't work. – Marc Gravell Jun 12 '11 at 07:44
  • The problem with arrays would apply just as well even if arrays did not support covariance, since one should theoretically able to able to store a Derived into a Base *and have the system still know it's a Derived*. Even if derived types had to be the same size as base types (perhaps by having base types predeclare the maximum size of a derived struct) the requirement that the system know whether an object is a base or derived type would still be a problem. If I had my druthers, though, structs would support a couple forms of inheritance-like behavior: ... – supercat May 14 '12 at 14:40
  • ...(1) Allow types to be declared as extending or aliasing other types (including structs); an extension or alias type would be stored at runtime as the base type, but could have added properties or functions (somewhat like extension functioins, but with clearer scoping rules); (2) Allow structs to have defined inheritance relations which would not apply to instances, but would apply to generic types, so a ColoredPoint which inherited Point and included color could be passed to a routine `MovePoint(ref T pt, int dx, int dy) where T:Point`. For the latter to be most helpful, the... – supercat May 14 '12 at 14:45
  • ...system would also have to provide a means of performing a "byref cast if possible" (so that if a function was called with a `T` that was some particular derivative of `Point`, its extra fields could be used). Note that composition could do some of what could be done with generics like those above, provided that the enclosed things were stored as exposed public fields, but some people dislike those. I doubt any such features will be added to .net, since some people like to disparage value types rather than try to fix their shortcomings. – supercat May 14 '12 at 14:50
  • Using c++ is not a good example as classes and stucts are only different in terms of default access. If your logic was valid then one could equally say c# classes don't support inheritance. – Paul Childs Mar 01 '21 at 23:18
78

Imagine structs supported inheritance. Then declaring:

BaseStruct a;
InheritedStruct b; //inherits from BaseStruct, added fields, etc.

a = b; //?? expand size during assignment?

would mean struct variables don't have fixed size, and that is why we have reference types.

Even better, consider this:

BaseStruct[] baseArray = new BaseStruct[1000];

baseArray[500] = new InheritedStruct(); //?? morph/resize the array?
Kenan E. K.
  • 13,955
  • 3
  • 43
  • 48
  • 7
    C++ answered this by introducing the concept of 'slicing', so that's a solvable problem. So, why shouldn't struct inheritance be supported? – jonp Aug 03 '09 at 16:16
  • 2
    Consider arrays of inheritable structs, and remember that C# is a (memory)managed language. Slicing or any similar option would wreak havoc on the fundamentals of the CLR. – Kenan E. K. Aug 03 '09 at 16:33
  • 5
    @jonp: Solvable, yes. Desirable? Here's a thought experiment: imagine if you have a base class Vector2D(x, y) and derived class Vector3D(x, y, z). Both classes have a Magnitude property which calculates sqrt(x^2 + y^2) and sqrt(x^2 + y^2 + z^2) respectively. If you write 'Vector3D a = Vector3D(5, 10, 15); Vector2D b = a;', what should 'a.Magnitude == b.Magnitude' return? If we then write 'a = (Vector3D)b', does a.Magnitude have the same value before the assignment as it does after? The .NET designers probably said to themselves, "no, we'll have none of that". – Juliet Aug 03 '09 at 16:41
  • 1
    Just because a problem can be solved, doesn't mean it should be solved. Sometimes it's just best to avoid situations where the problem arises. – Dan Diplo Aug 03 '09 at 16:42
  • @kek444: Having struct `Foo` inherit `Bar` should not allow a `Foo` to be assigned to a `Bar`, but declaring a struct that way could allow a couple of useful effects: (1) Create a specially-named member of type `Bar` as the first item in `Foo`, and have `Foo` include member names that alias to those members in `Bar`, allowing code which had used `Bar` to be adapted to use a `Foo` instead, without having to replace all references to `thing.BarMember` with `thing.theBar.BarMember`, and retaining the ability to read and write all of `Bar`'s fields as a group; ... – supercat Dec 17 '12 at 17:17
  • (2) It could be possible to have a generic member use type `T:Bar` and use `Bar`'s members on `T`. Without such ability, the only way to allow things like `Interlocked` updates to `Bar`'s members would be to have interface members which use such members mutate `this`. While that could certainly be done, the semantics of structs with mutating interfaces are very quirky, especially since there's no way to have a struct satisfy an `IFoo` constraint without it also allowing a representation-changing cast to an `IFoo`, even if such casts would almost never work as desired. – supercat Dec 17 '12 at 17:27
  • in C# all identifiers are references, so there is no excuse for the compiler not to box `b`, and hold a reference to `b` in `a`, with a warning from a linter maybe. – v.oddou Apr 17 '18 at 03:53
15

Structs do not use references (unless they are boxed, but you should try to avoid that) thus polymorphism isn't meaningful since there is no indirection via a reference pointer. Objects normally live on the heap and are referenced via reference pointers, but structs are allocated on the stack (unless they are boxed) or are allocated "inside" the memory occupied by a reference type on the heap.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • 10
    one does not need to use polymorphism to take advantage of inheritance – rmeador Aug 03 '09 at 15:36
  • 1
    So, you'd have how many different types of inheritance in .NET? – John Saunders Aug 03 '09 at 15:50
  • Polymorphism does exist in structs, just consider the difference between calling ToString() when you implement it on a custom struct or when a custom implementation of ToString() does not exist. – Kenan E. K. Aug 03 '09 at 16:00
  • That's because they all derive from System.Object. It's more the polymorphism of the System.Object type than of structs. – John Saunders Aug 03 '09 at 16:07
  • Polymorphism could be meaningful with structures used as generic type parameters. Polymorphism works with structs that implement interfaces; the biggest problem with interfaces is that they can't expose byrefs to struct fields. Otherwise, the biggest thing I think would be helpful as far as "inheriting" structs would be a means of having a type (struct or class) `Foo` that has a field of structure type `Bar` be able to regard `Bar`'s members as its own, so that a `Point3d` class could e.g. encapsulate a `Point2d xy` but refer to the `X` of that field as either `xy.X` or `X`. – supercat Oct 01 '13 at 20:56
  • @KenanE.K. - true. A better way of describing the limitation is that (unboxed) structs *require polymorphism to be resolved **at compile time***. The lack of a reference pointer makes it impossible to resolve **dynamically**. – ToolmakerSteve Nov 18 '21 at 01:40
7

Class like inheritance is not possible, as a struct is laid directly on the stack. An inheriting struct would be bigger then it parent, but the JIT doesn't know so, and tries to put too much on too less space. Sounds a little unclear, let's write a example:

struct A {
    int property;
} // sizeof A == sizeof int

struct B : A {
    int childproperty;
} // sizeof B == sizeof int * 2

If this would be possible, it would crash on the following snippet:

void DoSomething(A arg){};

...

B b;
DoSomething(b);

Space is allocated for the sizeof A, not for the sizeof B.

Dykam
  • 10,190
  • 4
  • 27
  • 32
  • 3
    C++ handles this case just fine, IIRC. The instance of B is sliced to fit in the size of an A. If it's a pure data type, as .NET structs are, then nothing bad will happen. You do run into a bit of a problem with a method that returns an A and you're storing that return value in a B, but that shouldn't be allowed. In short, the .NET designers _could_ have dealt with this if they wanted to, but they didn't for some reason. – rmeador Aug 03 '09 at 15:44
  • 1
    For your DoSomething(), there isn't likely to be a problem as (assuming C++ semantics) 'b' would be "sliced" to create an A instance. The problem is with arrays. Consider your existing A and B structs, and a DoSomething(A[] arg){arg[1].property = 1;} method. Since arrays of value types store the values "inline", DoSomething(actual = new B[2]{}) will cause actual[0].childproperty to be set, not actual[1].property. This is bad. – jonp Aug 03 '09 at 15:47
  • 2
    @John: I wasn't asserting it was, and I don't think @jonp was either. We were merely mentioning that this problem is old and has been solved, so the .NET designers chose not to support it for some reason other than technical infeasibility. – rmeador Aug 03 '09 at 15:51
  • It should be noted that the "arrays of derived types" issue isn't new to C++; see http://www.parashift.com/c++-faq-lite/proper-inheritance.html#faq-21.4 (Arrays in C++ are evil! ;-) – jonp Aug 03 '09 at 15:52
  • @John: the solution to "arrays of derived types and base types don't mix" problem is, as usual, Don't Do That. Which is why arrays in C++ are evil (more easily permits memory corruption), and why .NET doesn't support inheritance with value types (compiler and JIT ensure that it can't happen). – jonp Aug 03 '09 at 15:54
  • I can think of at least two sensible ways for the compiler to handle that that would never lead to a crash. Of course I can think of lots of ways to implement it that will crash, but why would you want to imlpement it that way? – Niki Aug 03 '09 at 16:31
6

Here's what the docs say:

Structs are particularly useful for small data structures that have value semantics. Complex numbers, points in a coordinate system, or key-value pairs in a dictionary are all good examples of structs. Key to these data structures is that they have few data members, that they do not require use of inheritance or referential identity, and that they can be conveniently implemented using value semantics where assignment copies the value instead of the reference.

Basically, they're supposed to hold simple data and therefore do not have "extra features" such as inheritance. It would probably be technically possible for them to support some limited kind of inheritance (not polymorphism, due to them being on the stack), but I believe it is also a design choice to not support inheritance (as many other things in the .NET languages are.)

On the other hand, I agree with the benefits of inheritance, and I think we all have hit the point where we want our struct to inherit from another, and realize that it's not possible. But at that point, the data structure is probably so advanced that it should be a class anyway.

Blixt
  • 49,547
  • 13
  • 120
  • 153
  • 6
    That is not the reason why there is no inheritance. – Dykam Aug 03 '09 at 15:33
  • I believe the inheritance being talked about here is not being able to use two structs where one inherits from the other interchangeably, but re-using and adding to the implementation of one struct to another (i.e. creating a `Point3D` from a `Point2D`; you would not be able to use a `Point3D` instead of a `Point2D`, but you wouldn't have to reimplement the `Point3D` entirely from scratch.) That's how I interpreted it anyways... – Blixt Aug 03 '09 at 15:42
  • In short: it *could* support inheritance without polymorphism. It doesn't. I believe it's a design choice to help a person choose `class` over `struct` when appropriate. – Blixt Aug 03 '09 at 15:49
  • @Blixt - no, it *couldn't* support inheritance, because structs deliberately lack the necessary method reference pointer. The design criteria is that a struct use as little memory as possible. In particular, when embedded in another entity, or in an array. So it only "could support inheritance" by sacrificing the only reason that structs exist at all! – ToolmakerSteve Nov 18 '21 at 01:43
  • @ToolmakerSteve You can do simple inheritance with stack allocated types. Have a look at embedded types in Go. I agree it's not possible to do polymorphic inheritance which you're talking about (and this is also mentioned above). – Blixt Nov 18 '21 at 15:40
  • @Blixt - ok, I misunderstood your previous comments. Do you agree that the fundamental issue isn't whether something is put on the stack or not, it is whether *the type is knowable at compile time*. If it is, then I agree - that could easily be supported, if language designers chose to. Its an implementation convenience (automatic method and field promotion), and all parties know at compile time which methods to call, and the size of the data. Does that match your view of it? – ToolmakerSteve Nov 18 '21 at 20:10
6

Structs are allocated on the stack. This means the value semantics are pretty much free, and accessing struct members is very cheap. This doesn't prevent polymorphism.

You could have each struct start with a pointer to its virtual function table. This would be a performance issue (every struct would be at least the size of a pointer), but it's doable. This would allow virtual functions.

What about adding fields?

Well, when you allocate a struct on the stack, you allocate a certain amount of space. The required space is determined at compile time (whether ahead of time or when JITting). If you add fields and then assign to a base type:

struct A
{
    public int Integer1;
}

struct B : A
{
    public int Integer2;
}

A a = new B();

This will overwrite some unknown part of the stack.

The alternative is for the runtime to prevent this by only writing sizeof(A) bytes to any A variable.

What happens if B overrides a method in A and references its Integer2 field? Either the runtime throws a MemberAccessException, or the method instead accesses some random data on the stack. Neither of these is permissible.

It's perfectly safe to have struct inheritance, so long as you don't use structs polymorphically, or so long as you don't add fields when inheriting. But these aren't terribly useful.

user38001
  • 99
  • 1
  • 3
    Almost. Nobody else mentioned the slicing problem in reference to the stack, only in reference to arrays. And nobody else mentioned the available solutions. – user38001 Aug 03 '09 at 22:29
  • 1
    All value types in .net are zero-filled on creastion, regardless of their type or what fields they contain. Adding something like a vtable pointer to a struct would require a means of initializing types with non-zero default values. Such a feature might be useful for a variety of purposes, and implementing such a thing for most cases might not be too hard, but nothing close exists in .net. – supercat May 14 '12 at 14:36
  • @user38001 "Structs are allocated on the stack" - unless they are instance fields in which case they're allocated on the heap. – David Klempfner Nov 16 '17 at 23:05
3

There is a point I would like to correct. Even though the reason structs cannot be inherited is because they live on the stack is the right one, it is at the same a half correct explanation. Structs, like any other value type can live in the stack. Because it will depend on where the variable is declared they will either live in the stack or in the heap. This will be when they are local variables or instance fields respectively.

In saying that, Cecil Has a Name nailed it correctly.

I would like to emphasize this, value types can live on the stack. This doesn't mean they always do so. Local variables, including method parameters, will. All others will not. Nevertheless, it still remains the reason they can't be inherited. :-)

Rui Craveiro
  • 2,224
  • 16
  • 28
  • *"the reason structs cannot be inherited is because they live on the stack is the right one"* - no, it isn't the reason. – ToolmakerSteve Nov 18 '21 at 01:55
  • 1
    A variable of a ref type will contain a reference to an object in the heap. A variable of a value type will contain the value of the data itself. The size of the data must be known at compile-time. This includes local variables, which includes parameters, which all live in the stack. Thinking about it the size of all object fields must be known during object allocation as well. So, I accept the stack a special case of a general reason, but it is still a reason, though. – Rui Craveiro Nov 18 '21 at 15:26
  • 1
    When you put it that way, I agree. I was thinking about the other half of inheritance, where it is impossible to work with the data because the data doesn’t include a pointer to a class ref, so it is not knowable which subclass (sub-struct?) the data is from. Its just a meaningless sequence of bits. – ToolmakerSteve Nov 18 '21 at 19:13
2

This seems like a very frequent question. I feel like adding that value types are stored "in place" where you declare the variable; apart from implementation details, this means that there is no object header that says something about the object, only the variable knows what kind of data resides there.

Cecil Has a Name
  • 4,962
  • 1
  • 29
  • 31
  • The compiler knows what's there. Referencing C++ this cannot be the answer. – H H Aug 03 '09 at 15:56
  • Where did you infer C++ from? I'd go with saying in-place because that's what matches the behaviour the best, the stack is an implementation detail, to quote an MSDN blog article. – Cecil Has a Name Aug 03 '09 at 19:57
  • Yes, mentioning C++ was bad, just my train of thought. But aside from the question if runtime info is needed, why shouldn't structs have an 'object header' ? The compiler can mash them up anyway it likes. It could even hide a header on a [Structlayout] struct. – H H Aug 03 '09 at 20:40
  • Because structs are value types, it is not necessary with an object header because the run-time always copies the content as for other value types (a constraint). It wouldn't make sense with a header, because that's what reference-type classes are for :P – Cecil Has a Name Aug 04 '09 at 06:22
1

Structs do support interfaces, so you can do some polymorphic things that way.

Wyatt Barnett
  • 15,573
  • 3
  • 34
  • 53
0

IL is a stack-based language, so calling a method with an argument goes something like this:

  1. Push the argument onto the stack
  2. Call the method.

When the method runs, it pops some bytes off the stack to get its argument. It knows exactly how many bytes to pop off because the argument is either a reference type pointer (always 4 bytes on 32-bit) or it is a value type for which the size is always known exactly.

If it is a reference type pointer then the method looks up the object in the heap and gets its type handle, which points to a method table which handles that particular method for that exact type. If it is a value type, then no lookup to a method table is necessary because value types do not support inheritance, so there is only one possible method/type combination.

If value types supported inheritance then there would be extra overhead in that the particular type of the struct would have to placed on the stack as well as its value, which would mean some sort of method table lookup for the particular concrete instance of the type. This would eliminate the speed and efficiency advantages of value types.

Matt Howells
  • 40,310
  • 20
  • 83
  • 102
  • 1
    C++ has solved that, read this answer for the real problem: http://stackoverflow.com/questions/1222935/why-dont-structs-support-inheritance/1223227#1223227 – Dykam Aug 03 '09 at 16:21