Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussion: Stack stored classes, Heap stored "value classes" #460

Closed
lachbaer opened this issue Apr 19, 2017 · 121 comments
Closed

Discussion: Stack stored classes, Heap stored "value classes" #460

lachbaer opened this issue Apr 19, 2017 · 121 comments

Comments

@lachbaer
Copy link
Contributor

lachbaer commented Apr 19, 2017

For heap stored "value classes" please see #460 (comment)

Question

Why is it not possible to put classes on the stack? Value type structs do not offer the comfort of classe swhen it comes to inheritance.

In C this is possible by ommiting the new keyword.

I can think of the reason, that accidentially ommiting new and creating a stack stored instance without any further notice can lead to unwanted behaviour. Also when creating the (first) CLR, strictly distinguishing between stack stored struct and heap stored and garbage collected class might be easier.

Motivation

There is ongoing development with (non-)nullable reference types, et alta. It seems that the borders between value types (struct) and reference types (class) partly obliberate nowadays.

I recently had scenarios where putting a class type on the stack to accelerate processing would be quite useful.

Possible syntax

To create a class instance on the stack the following statement could be used, where new is replaced by struct. ClassBar# marks innerBar as being not a pointer.

ClassBar# innerBar = struct ClassBar();

To (flat) copy a heap stored instace to the stack, a struct() operator can be introduced

var refPoint = new ReferencePoint(0.0, 0.0);
var pointOnStack = struct(refPoint);

Implementation possibilities

  • As the size of a managed reference type can massively vary, the instance can be stored in a dedicated area of the heap
  • When leaving the scope of the variable, i.e. when the stack is reduced, the finalizer gets called immediately
  • This would most probably require a CLR change

Alternatives

Maybe the practical performance impacts nowadays aren't as heavy, so that leaving classes on the managed heap is actually no issue any more.

@lachbaer
Copy link
Contributor Author

And I do not see how emoticon reactions and unargumented statements like the the one above answer the quesion and lead to a serious discussion! 😞

I ask a further question, and I would be lucky to get real answers on that!

How high are the costs of creating a class object on the managed heap, instead of creating an identical struct instance on the stack?

@HaloFour
Copy link
Contributor

This would most probably require a CLR change

This would certainly require a CLR change. Even C++/CLI and MC++ are unable to allocate .NET classes on the stack.

For reference:

dotnet/roslyn#2104
https://github.com/dotnet/coreclr/issues/1784
http://xoofx.com/blog/2015/10/08/stackalloc-for-class-with-roslyn-and-coreclr/

@lachbaer
Copy link
Contributor Author

@HaloFour Thanks for the valuable links 😃

On my processor the runtime difference of an optimized compilation between allocating a class (without any field initializations) and an identical struct is between 10:1 and 20:1.

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 19, 2017

ClassBar firstBar = new ClassBar();
ClassBar #secondBar = new ClassBar();

When the compiler reaches the second new ClassBar() it must "look back" for what memory type it should create the object. Also you can accidentially omit or delete the # and get a different result than wanted.
Therefore I think struct ClassBar() is better in terms of clearness (also for the compiler) and stability.

I don't see your point on nullables. Nullable<T> is a value type on the stack and can be null. # marked variables must never and cannot be null.

@HaloFour
Copy link
Contributor

Stack allocation is certainly much faster. But it comes with a lot of limitations, especially if the C# compiler is to attempt to enforce safe memory management. I think that the cases that this feature could be applied would be incredibly limited as a result. You couldn't take an arbitrary class and just "pin" it to the stack. The class would likely have to meet a very strict set of guidelines that would have to be enforced by the CLR. For example the class might have to be sealed to prevent inheritance which would prevent the compiler/JIT from ensuring that enough space is allocated on the stack.

Then you have the entire problem of escape analysis. That reference can't go anywhere since it points to a portion of memory that is unmanaged. I'm not entirely sure how that can be adequately handled since effectively calling any instance method on the reference is a form of escaping since those methods could save this literally anywhere. The blog post above gets into this a little bit as this was already brought up as one of the major hurdles.

@gafter
Copy link
Member

gafter commented Apr 19, 2017

@lachbaer Objects on the heap are freed by the GC, which ensures that the object survives until after the point when its last reference is no longer reachable. You have not explained to us how you would accomplish that for objects that are on the stack, or what the behavior should be when there are dangling references. So we cannot answer how expensive it would be.

@lachbaer
Copy link
Contributor Author

@gafter
Objects on the stack should behave like structs, in a way.

What I mean is, that once the stack is cleared, the object is immediately finalized.

To ensure that no dangling references occur, the stack object gets a new type name suffixed with a #. The variable is handled like a struct from that point on, i.e. there can be no further references to it. The instance on the stack is a shallow copy of the heap object ( = struct(objVariable)) or a newly created object (= struct Object()).

Also, an "object# obj = struct Object();" is by default converted to a sealed type. It must not be cast to any other type in the hierarchy. The moment that it is cast to (object) or an explicit interface type it is "boxed" again, like any value type, and by that regains all its powers.

The object on the stack is not a reference, it is a copy. It's like int? a = b with int b, where int? is a new type, but now with the type modifier #. Boxing by assigning the stack object to an object obj again makes a copy makes of the stack object on the heap and keeps the stack object on the stack until being out of scope.

A valid issue is when the finalizer frees resources, that are pointed to by reference fields of the object. There can be dependencies between the objects that could lead to undecicive behaviour.

@HaloFour
Copy link
Contributor

@lachbaer

The variable is handled like a struct from that point on, i.e. there can be no further references to it.

You have to be able to take references to the copy on the stack in order for instance methods to work.

@lachbaer
Copy link
Contributor Author

@HaloFour Seems that I have a lack of knowledge here. What is the difference on how instance methods of structs work compared with reference types? You can use this in value type "instance" methods. ❓

@HaloFour
Copy link
Contributor

@lachbaer

In the case of struct instance methods the compiler pushes a ref to the struct onto the stack. That's why struct instance methods can be self-mutating.

@lachbaer
Copy link
Contributor Author

@HaloFour But that means that the ref to itself lies deeper (physically) in the stack and can only exists when the instance itself exists. The ref will always be purged before the actual object. Then this is no technical issue and you just get confused by the statement "there can be no further references to it"? Or do I oversee something?

@mikedn
Copy link

mikedn commented Apr 19, 2017

To ensure that no dangling references occur, the stack object gets a new type name suffixed with a #

So you're suggesting that the C# compiler should create a struct type and copy the reference type's fields and methods?

@lachbaer
Copy link
Contributor Author

@mikedn I think that there must be a CLR change for this and that the CLR does this in the background. The compiler creates the type only as a TypeSymbol.

@svick
Copy link
Contributor

svick commented Apr 19, 2017

@lachbaer What would happen in the following code?

class C
{
    private static C storage;

    private int state;

    public int M()
    {
        storage = this;
        storage.state = 42;
        return this.state;
    }
}C c1 = new C();
Console.WriteLine(c1.M());

C# c2 = struct C();
Console.WriteLine(c2.M());

As far as I can tell, there are two options:

  1. C#.M is the same method as C.M. When you call it, a reference to the C stored on the stack is passed in, resulting in memory corruption down the line.
  2. C#.M is a copy of C.M, but with semantics changed to be safe (e.g. by copying the contents of this when passed elsewhere). I don't see how could this be done without changing the semantics so much that huge number of methods (like C.M) become broken.

What am I missing? Is there some third option? Or do you think one of the two options above is acceptable?

@HaloFour
Copy link
Contributor

What's more the IL for both the struct and the class implementation of such behavior is different, both in the implementation and the consumption. It wouldn't be possible to treat a class like a struct with the same IL generated for that class.

@dstarkowski
Copy link

Objects on the stack should behave like structs, in a way

Is it only in terms of memory or are classes allocated on stack passed to a method by value as well? Can parameter of type ClassBar# be passed to method that accepts ClassBar?

If not you won't be able to use it with any existing code. Otherwise you need to get reference, which you stated shouldn't be possible.

@lachbaer
Copy link
Contributor Author

@HaloFour

It wouldn't be possible to treat a class like a struct with the same IL generated for that class

Can you submit an example? You are probably right, but I haven't seen solvable differences in the IL. A CLR change must be done for this anyhow.

@svick

What would happen in the following code?

I admit not to think of self-references. 😞 That will crash whatever way I think. But I weren't me, if I hadn't had a solution in my mind 😀 :

The purpose of this construct in the first place is to allow a relevant performance boost for lightweight classes, who are classes and not structs for some reason. Most of those classes I can think of are part of my projects, so I have access to them, or will probably be lightweight classes of the framework, that could be rededicated easily.

If backward compatibility is no issue - because until now we dealt with the current situation somehow - a new "kind" of class can be introduced to the language and CLR, namely class#.

public class# Person { ... }
public class# Student : Person { ... }

Some assurances must be made about this classes, two being

  1. Inheritance can only be made on stackable classes, i.e. other class#'es
  2. this is only allowed on other T# declarations.

This means, that not per se all classes can be put on the stack, but we would have the opportunity to create new classes for which this feature would actually (and only) make sense. (Besides we get the benefit of an implicit "Dispose" by a deterministic called finalizer.)

@dstarkowski

Can parameter of type ClassBar# be passed to method that accepts ClassBar

Yes, it will be shallow copied on the heap, loosing its performance benefit. The method then operates on a copy of the object! But there is a solution to that, too:

void RenameStudent(Student stud) { stud.Name += " the douche"; }
Student# student = new Student#("Eric");
RenameStudent(student ref);

After RenameStudent exits the heaped student is copied back to the stacked student. That is indicated by appending the ref keyword to the argument.

@mikedn
Copy link

mikedn commented Apr 20, 2017

a new "kind" of class can be introduced to the language and CLR, namely class#

Not "can", "must". There is no other way to ensure that a reference type allocated on stack doesn't use this in an unsafe manner.

Yes, it will be shallow copied on the heap, loosing its performance benefit. The method then operates on a copy of the object! But there is a solution to that, too:

So you're proposing that significant changes are made to the runtime and the language in the name of performance but it all falls flat on its face as soon as the usage becomes slightly more complicated. It's probably cheaper and more effective to let the runtime do escape analysis and stack allocate reference types that do not escape the stack.

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 20, 2017

When looking at all the issues I have posted upon recently - of course including this one 😁 - it kind of seems to me that we/I treat the symptom, not the cause.

With acceptable CLR changes being unavoidable at least thinking about a completely new native type, not being backwards compatible, is acceptable. I'll call it cluct or strass 😆 No, seriously, this time I go with struct# to facilitate keyword reuse.

It will bring several things together

  • Allow for initializers and no-arg constructors
  • Allow for limited inheritance
  • Allow for unboxed interfaces
  • Implicit type safe boxing (no to and from object conversions)
  • Allow for swip and swap between stack and heap without implications
  • Deterministic finalizer when out-of-scope
  • CLR optimized performace
  • Forced value-type-nullability by using struct? (implies struct#?)

That struct# had basically quite a lot of the classical struct in essence, but loosens many if not all constraints that made me and other participants propose this or that approach to solve a current issue.

Addendum: to facilitate some of the characteristics of that new type, an additional, specialized heap with less CLR overhead can be created in memory. The stack then simply stores the pointer.

@HaloFour
Copy link
Contributor

Sounds like a massive amount of work for very little real benefit.

@lachbaer
Copy link
Contributor Author

@HaloFour The primary benefit lies in performance. In very many cases you operate on midweight objects that are already too heavy for being structs but don't make much or any use of comprehensive class features. Nevertheless you finally decide for classes.

The CLR is initially designed to support the broad OOP feature of classes, but the costs for that managed behaviour is massive. Performance measures underline that and for some this is the contra-argument to not go with .NET, Java or alike.

With this, admittedly big, addition to the CLR many often used (custom) classes nearly get the performance of structs while still maintaining the most used comfort of classes. The performace jump will be groundbreaking, towards a Pro for .NET into the next decade(s). And also a possible beater against the JVM for .NETCore.

@dstarkowski
Copy link

@lachbaer

In very many cases you operate on midweight objects that are already too heavy for being structs

What makes object too big for being a struct, but not too big for stack allocated class?

@mikedn
Copy link

mikedn commented Apr 20, 2017

The primary benefit lies in performance

And as already stated there are alternatives to this proposal that are likely easier to implement.

@HaloFour
Copy link
Contributor

@lachbaer

That's why we have ref locals and returns now. The size of the struct no longer matters, you don't have to copy it around to work with it.

@fanoI
Copy link

fanoI commented Apr 20, 2017

I continue to think when I read these genre of issues (stackalloc, destructible types, this "strass") that there is fundamental fallacy: C# is NOT C/C++ and I don't want to have to preoccupy of the memory management!

This is should be the work of the Jitter / AOT compiler doing the correct escape analysis and then allocating objects on the stack only when it is safe to do; the objection that is impossible does not make sense: Java has this from years and the JVM and the CLR are really a lot similar!

Stack analysis will give this all for free:

  1. Classes are allocated into the stack when possible only when strictly needed are on the heap
  2. If one class has been "deboxed" to be a struct its finalizer could be called directly when the stack is unwinden, while the user should write "using var f = new FileStream()" that is converted to a try / catch / finally with an explicit "f.Dispose()" after the escape analysis has realized that indeed 'f' does not escapes can make all the try / catch / finally disappear and call f.Dispose or more correctly yet ~f() when 'f' goes out of scope
  3. A lot of "concerns" on some part of .Net for performances (Linq, Enumerable, ...) will "magically" be gone away in the 99% of the cases they will be allocated on the stack!
  4. Classes that contain other classes will be onto the stack without the need of a reference (pointer) to the other class, no need to indirection if it does not escapes!

In the end the boundary between ValueTypes (technically an "hack") and Objects will become indeed totally nonexistent an hypothetical new .Net could not have this distinction and be a pure object oriented language (as Java would wanted to be but then they feel the necessity of "primitive types" and in some way broken it) in which Integer is an Object but 99% of the times is allocated on the stack as if it was a "native" int.

@jnm2
Copy link
Contributor

jnm2 commented Apr 20, 2017

cluct or strass

Please stop with the portmanteaus, lol...

@mikedn
Copy link

mikedn commented Apr 20, 2017

In the end the boundary between ValueTypes (technically an "hack") and Objects will become indeed totally nonexistent

Not really. For one thing escape analysis is limited in what it can do (e.g. it's very difficult to allocate on the stack objects that are returned). And more importantly, being a value type and being allocate on the stack are independent things.

an hypothetical new .Net could not have this distinction and be a pure object oriented language

Value types have nothing to do with C# being or not being a "pure" OOP language. Not to mention that the idea of a "pure" OOP language is archaic.

@CyrusNajmabadi
Copy link
Member

I WANT, NO NEED, STRUCT LIKE VALUE TYPE SEMANTICS WITHOUT THE RESTRICTIONS OF CLASSIC STRUCTS.

Then use a class with value semantics. We have tons of them all over the place. The most classic example is "System.String".

Again you haven't been able to explain why existing solutions are not sufficient. If you want this sort of thing then you can write such a thing using an attribute + a roslyn analyzer. If you can't explain to me why that's insufficient, and why this requires a language change, then i'm not going to have any reason to feel like we should add this.

@lachbaer
Copy link
Contributor Author

lachbaer commented Apr 24, 2017

and then not being able to explain why

Haeh??????? What am I doing here the whole time....????????????????? I stop now.
I could also say "you're not being able to understand" - it's the same!

@CyrusNajmabadi
Copy link
Member

@lachbaer Please stop giving people thumbs-downs for asking you for the situations where this would be valuable.

If you are proposing language changes, then the onus is on you to be able to provide clear and suitable justifications for why those language changes are worthwhile. Right now you still haven't been willing to answer my question about why you couldn't accomplish this today with just an attribute and an analyzer.

You also haven't put forth a compelling scenario where i woudl want this. Your fraction example is actually a really poor example to me. If i had a fraction type, why would i want to be forced to take an allocation hit on every assignment? That seems like a very bad way to design this API.

If you want me to be convinced of your position, you need to provide an actual use case where this sort of enforced behavior is desirable.

@lachbaer
Copy link
Contributor Author

Having language support for #= forces a pattern.
Better have a pattern than 20 different programmers having 30 different analyzers for 40 ways and functions to assign a reference or value.
When you look at #99 there are mentioned other examples from valued members for types that should be 'struct' for i.a. semantic reasons but suffer from restrictions. For #99 there is currently no consent of a final solution. My idea here (I don't call it proposal) could partly address that.
By having an '#=' operator the programmers are able to write their algorithms nearly as they would if the types were structs. With structs you will use '='. The need for method calls gives a completely different experience.
And as mentioned at the beginning, it gives a coherent experience instead of individual variants (method names).

It is a bit like introducing 'type?' for Nullables. You don't actually need it, you could use the Nullable methods, like in the early days of VB. Or a bit like '??'. You could express that differently (besides the performance benefit). I think '#=' fits in the same category for the reasons mentioned in the previous paragraph.

@CyrusNajmabadi
Copy link
Member

CyrusNajmabadi commented Apr 24, 2017

It is a bit like introducing 'type?' for Nullables. You don't actually need it, you could use the Nullable

That's not actually the case :-) There were necessary changes to the runtime to make Nullable work that could not be achieved with actual pure language support. :)

For example, say that Nullable<T> was just a struct like any others. Then what would happen in this case:

Nullable<int> i = default(Nullable<int>);
object o = i;

What is 'o' here? If 'Nullable' is just a normal struct, then 'o' would actually be a boxed version of that struct. But that's not the case. Nullable<T> is special to the runtime and that code will actually produce 'o' with a true value of 'null' in it.

@CyrusNajmabadi
Copy link
Member

@lachbaer Would you be willing to discuss any of this offline? You can reach me at cyrusn@microsoft.com. thanks!

@lachbaer
Copy link
Contributor Author

Don't take me too literally on the Nullables. I meant things like "if (x.HasValue)" instead of language supported "if (x != null)".

Also '#=' would give a programmer the ability to just have a custom assingment operator. In the guides is stated that you cannot overload the standard '=' assignment operator. Well, if actually nobody asked for that or tries it then you delete that statement from the guides and save that digital ink 😉

@lachbaer
Copy link
Contributor Author

@CyrusNajmabadi I'll write you. But know I have to leave the PC. Good night. (03:21 here).

@lachbaer
Copy link
Contributor Author

@HaloFour

This is what ICloneable is for.

Of course, I haved used that already sometimes 😱 Just didn't think of using it in my Fraction above.
Well, when sticking with the '#=' operator for the above mentioned experience it could default to 'Clone()' unless individually overloaded.

@CyrusNajmabadi
Copy link
Member

@lachbaer Any opportunity to talk?

@lachbaer
Copy link
Contributor Author

@CyrusNajmabadi You've got mail...

@lachbaer
Copy link
Contributor Author

lachbaer commented May 3, 2017

Idea update:
Because '#=' looks ugly, I now suggest to use := as the value-assignment-operator for reference types. It can default to IClonable.Clone() if an implicit conversion from rhs.Clone() to lhs exists. Otherwise new LHS(rhs) is called, if existent. In all other cases (e.g. value types) it behaves exactly like the standard =, with the exception that never a reference to RHS is assigned, in that case a compile time error occurs.

@dstarkowski
Copy link

I'm totally lost with how it evolved from stack allocated class through heap allocated struct, couple of other stages I'm not sure anybody understood, into Roslyn analyzer and finally syntactic sugar for IClonable.Clone() method.

However I do not like the idea of new operator. Especially if it does three different things based on implemented interfaces and constructors and only works for quite limited subset of types. More obfuscation than help to me.

@whoisj
Copy link

whoisj commented May 3, 2017

Agreed with @dstarkowski , this whole proposal has gotten messy.

I'm starting to think that support for Default Interfaces + Parameter-less Struct Constructors should be sufficient to meet the needs of this proposal. That is, if they get accepted and implemented.

@lachbaer
Copy link
Contributor Author

lachbaer commented May 3, 2017

I admit that it got very clumsy all the way down.

The discussion about parameterless constructors (#99) in structs and it's vast attendance - my interest in it, too - made me think about alternatives that give the users what they intend with it.

  1. fast performance (struct)
  2. constructors, a definite init instance state other than zero (class)
  3. non-nullability (struct again)

The only way to achieve that would be a completely new type class ('strass' 😉 ). But for sure the immense work to the CLR that this would imply is most probably not worth it. Hey, we lived without it 15 years already. 😁

That is why I finally came up with the idea of creating something like an assign-by-value operator. Of course that is only a compromise, but may help using a class more struct like and guard agains typical errors that arise when accidentially assigning a class by value instead of cloning it. That's the whole point.

We can define mathematical (-like) operators on classes, yet we cannot define a mathematical-like assignment operator. We must either use a Clone method or a copy constructor.

To me that makes much sense. Maybe it needn't be as complicated as stated above. := could invoke a copy-constructor if present (class and struct), do a memberwise copy (struct) or error if class has no copy-c'tor or flow analysis shows a null assignment. Sounds more complicated than it practically is.

@jnm2
Copy link
Contributor

jnm2 commented May 3, 2017

If I hear the word strass one more time... 😝

@HaloFour
Copy link
Contributor

HaloFour commented May 3, 2017

@jnm2

Is it strassing you out?

@lachbaer
Copy link
Contributor Author

lachbaer commented May 5, 2017

Sometimes a possible solution to value-type-classes can be easy, in terms of syntax additions. Why not simply defining a struct class (no, not a strass or cluct 😛 ) that brings no extra syntax changes. This is a compiler internal pseudo type class, relying on a standard CLR class with an attribute, that just follows some easy rules.

public struct class Fraction {
    public double Numerator = 0d;
    public double Denominator = 1d;
}
  • As with value types, default(SC) and new SC() are the same. Because internally this is class, in those cases always new SC() is called.
  • If the struct class has no standard constructor, one is synthesized.
  • null assignment is not allowed, if not typed nullable T? in which case it simply is a real null reference.
  • A variable declaration has no further modifiers. Like with structs and classes you cannot tell one from the other by simply looking at the declaration.
MyClass mc = new MyClass();
MyStruct ms = new MyStruct();
MyStrass msc = new MyStrass();
// or ... = default;
  • Assignments always go by value, i.e. memberwise cloning, identical to structs.
  • ref works like for structs as well, it kind of converts a struct class back to a class.
  • inheritance is only allowed from other struct classparents.

If such a type is published or referenced by other assemblies it must deal with nullability, because it is an actual CLR class. However other .NET languages can identify this type class by an attribute ('ValueClassAttribute') and make it a struct class for their compilation as well.

In generics it is internally handled like a standard class. But the calls to the generic functions are proxied and arguments and return values are cloned to a new instance, like with a standard struct.

@whoisj
Copy link

whoisj commented May 5, 2017

Why not simply defining a struct class (no, not a strass or cluct 😛 ) that brings no extra syntax changes.

Because that's not self describing and confusing as all get out. Though, I do think that you're on to something here. Maybe a value class might make more sense?

The other option (I keep pointing out) is to just make struct a first class citizen and allow inheritance, parameter-less constructors, and the like.

@HaloFour
Copy link
Contributor

HaloFour commented May 5, 2017

@whoisj

The other option (I keep pointing out) is to just make struct a first class citizen and allow inheritance, parameter-less constructors, and the like.

How do you propose that? The limitations imposed on structs are there for a reason; structs need to have a predictable size. Any capacity to actually inherit from a struct would have to be severely limited.

@whoisj
Copy link

whoisj commented May 5, 2017

@HaloFour I'm confused as to how inheritance would make the size of a struct non-deterministic? Can you elaborate?

@lachbaer
Copy link
Contributor Author

lachbaer commented May 5, 2017

@HaloFour
Copy link
Contributor

HaloFour commented May 5, 2017

@whoisj

If you have the following:

struct A {
    public int X;
}

struct B : A {
    public int Y:
}

How much memory does the CLR need to allocate in order to have a variable of type A? Or an A[10]? A itself is (likely) four bytes, but if you can assign a B to a variable of type A you need (likely) eight bytes.

Now if the compiler/CLR considers B and A to be wholly incompatible types and it's illegal to assign a B to an A then maybe that issue could be avoided, but that would make for a pretty big limitation.

@lachbaer
Copy link
Contributor Author

lachbaer commented May 5, 2017

Putting all theory besides. Does anyone has a serious real world example where value-type-semantics-only for a class would make sense? The only one I can currently think of is a n-dimensional matrix, so mathematical objects that are too big for the stack. For those cases an analyzer that bewares of reference assignments would already be sufficient?

@HaloFour
Copy link
Contributor

HaloFour commented May 5, 2017

This would be a completely separate proposal but the compiler might be able to fake struct inheritance without any CLR changes:

public struct A {
    private int X;

    public A(int x) {
        this.X = x;
    }

    public int X { get => this.x; }

    public void PrintIt() {
        Console.WriteLine($"A.PrintIt: X = {this.X}");
    }
}

public struct B : A {
    private int Y;

    public B(int x, int y) : base(x) {
        this.Y = y;
    }

    public int Y { get => this.y; }

    public override void PrintIt() {
        Console.WriteLine($"B.PrintIt: Y = {this.Y}");
        base.PrintIt();
    }
}

// B is equivalent to
public struct B {
    private A $base;
    private int y;

    public B(int x, int y) {
        this.$base = new A(x);
        this.y = y;
    }

    public int X { get => this.$base.X; }
    public int Y { get => this.y; }

    public void PrintIt() {
        Console.WriteLine($"B.PrintIt: Y = {this.Y}");
        this.$base.PrintIt();
    }
}

Not sure it's really worth it, though.

@lachbaer
Copy link
Contributor Author

lachbaer commented May 5, 2017

@HaloFour Not much code saved, actually. Nevertheless, 👍 for the solution!

I wonder, why the discussions on struct are so vivid here on this repo? I sometimes heard that the costs are too high and the real usage is too little to justify any improvements or changes. However, why do most diverse programmers engage then in these discussion so enthusiastically?

@whoisj
Copy link

whoisj commented May 5, 2017

@HaloFour thanks for that, I was not considering the case of A[] being an allocation of B : A. Makes sense - and brings me back to my roots of severely disliking inheritance.

@lachbaer
Copy link
Contributor Author

lachbaer commented May 8, 2017

The discussion got quite messy all the way down.

The only question finally left is whether there should be language support give special classes value type usage, e.g. with assign-by-value semantics. But that is another discussion.

@lachbaer lachbaer closed this as completed May 8, 2017
@jnm2
Copy link
Contributor

jnm2 commented Oct 4, 2018

Fyi, work has started on stack-allocated objects. dotnet/coreclr#20251

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests