Why structs cannot have destructors?
Asked Answered
H

4

31

What is best answer on interview on such question you think?

I think I didn't find a copy of this here, if there is one please link it.

Hulk answered 26/11, 2011 at 3:25 Comment(7)
I wouldn't ask this in an interview, since I like to ask questions that are mostly relevant to actual job experience, both past and future. If you want to see how someone reasons through obscure corner cases or see how they react to being presented such a question in an interview then it might fit into a larger battery of questions. Otherwise it is basically trivia. I'd give them points for thinking through it well, and for pointing out that it was something you probably wouldn't encounter in the wild. Mentioning it was a nonsense question might earn points, depending on how they did it.Reiners
I know a place where they ask this question on interview. I thought there are various types of roads one can go answering it. I like currently upvoted answer. Also from what I know this interviewer doesn't ask questions relating lambdas/linq but he does ask this pretty much useless question (imho). Especially cause I know domain is very far from struct level, and I believe there are less than 0.5% of structs in that project, actual number is probably even smaller. So odd question indeed, also really not sure what they are trying to test there.Hulk
PS: given above description I doubt that mentioning that its a nonsense question is going to earn us points on such interview :) next question of-course is - do we really want to work there.Hulk
Yes, with that interviewer it might not be good to point out that he's asking trivia. With me as an interviewer it would. That's what I meant to say :) As for lambda and Linq questions, I wouldn't avoid them, but I wouldn't ask about them unless the candidate showed interest. Language features are great, and gauging their interest in language features is important. But I'd wait until they showed that they knew and were interested in those language features until I asked directly about them. Same with design patterns, dependency injection, various .Net libraries, and many other things.Reiners
hm this is getting more interesting. Could you elaborate "candidate shows interest". Lets say candidate is sitting across you, little stressed as usual. How does he start to show interest in such various topics like DI, linq, patterns, unless you ask him about those yourself?Hulk
@ValentinKuzub: You get him/her talking about recent projects they were involved in. While it's only possible that their recent project involved those things specifically (LINQ, patterns, etc.), they will inevitably involve something worth asking further questions about if the candidate had a genuine interest in the project.Singlet
I wouldn't always. Some positions need warm bodies, some need good team players, some need people who will push boundaries on your team. So it depends on the candidate and the position. If someone is clammed up disproportionately to their level, you might consider them for a less critical position, or might disqualify them entirely based on soft skills. It's partly your job to loosen them up, though - there's two sides to that street :) Beyond that, it's like Joel said, with the addition that it is okay to ask about extracurricular programming interests, especially if it's on their resume.Reiners
U
69

Another way of looking at this - rather than just quoting the spec which says that structs can't/don't have destructors - consider what would happen if the spec was changed so that they did - or rather, let's ask the question: can we guess why did the language designers decide to not allow structs to have 'destructors' in the first place?

(Don't get hung up on the word 'destructor' here; we're basically talking about a magic method on structs that gets called automatically when the variable goes out of scope. In other words, a language feature analogous to C++'s destructors.)

The first thing to realize is that we don't care about releasing memory. Whether the object is on the stack or on the heap (eg. a struct in a class), the memory will be taken care of one way or another sooner or later; either by being popped off the stack or by being collected. The real reason for having something that's destructor-like in the first place is for managing external resources - things like file handles, window handles, or other things that need special handling to get them cleaned up that the CLR itself doesn't know about.

Now supposed you allow a struct to have a destructor that can do this cleanup. Fine. Until you realize that when structs are passed as parameters, they get passed by value: they are copied. Now you've got two structs with the same internal fields, and they're both going to attempt to clean up the same object. One will happen first, and so code that is using the other one afterwards will start to fail mysteriously... and then its own cleanup will fail (hopefully! - worst case is it might succeed in cleaning up some other random resource - this can happen in situations where handle values are reused, for example.)

You could conceivably make a special case for structs that are parameters so that their 'destructors' don't run (but be careful - you now need to remember that when calling a function, it's always the outer one that 'owns' the actual resource - so now some structs are subtly different to others...) - but then you still have this problem with regular struct variables, where one can be assigned to another, making a copy.

You could perhaps work around this by adding a special mechanism to assignment operations that somehow allows the new struct to negotiate ownership of the underlying resource with its new copy - perhaps they share it or transfer ownership outright from the old to the new - but now you've essentially headed off into C++-land, where you need copy constructors, assignment operators, and have added a bunch of subtleties waiting to trap the unaware novice programmer. And keep in mind that the entire point of C# is to avoid that type of C++-style complexity as much as possible.

And, just to make things a bit more confusing, as one of the other answers pointed out, structs don't just exist as local objects. With locals, scope is nice and well defined; but structs can also be members of a class object. When should the 'destructor' get called in that case? Sure, you can do it when the container class is finalized; but now you have a mechanism that behaves very differently depending on where the struct lives: if the struct is a local, it gets triggered immediately at end of scope; if the struct is within a class, it gets triggered lazily... So if you really care about ensuring that some resource in one of your structs is cleaned up at a certain time, and if your struct could end up as a member of a class, you'd probably need something explicit like IDisposable/using() anyhow to ensure you've got your bases covered.

So while I can't claim to speak for the language designers, I can make a pretty good guess that one reason they decided not to include such a feature is because it would be a can of worms, and they wanted to keep C# reasonably simple.

Umpteen answered 26/11, 2011 at 5:3 Comment(6)
A tremendous answer! I actually always seek such answers for this type of questions, the "design decision" as opposed to the "final decision". +1.Picket
Destructors would make sense for structures if and only if structures had a means of overriding the default copy constructor.Pacifist
@Pacifist I absolutely agree. Now, the question is why not allow for that? In my case, I want to implement a hassle-free system of reference counting for use in object pooling. (Why? Because in real-time applications, such as games, non-deterministic garbage collection can be death. The object pool can ensure that unless there is serious memory pressure or some explicit "loading" phase, memory will be re-used, and never de-allocated.)Quiberon
@Domi: If structures can't have constructors, then arrays can be constructed merely by filling the new allocation with zero. Supporting structure constructors would greatly complicate array construction, especially considering the possibilities that (1) structures may be nested, and (2) a constructors might fail in the middle of initializing an array.Pacifist
"Now you've got two structs with the same internal fields, and they're both going to attempt to clean up the same object. One will happen first, and so code that is using the other one afterwards will start to fail mysteriously" - If destructors just call Dispose(false), and Dispose() is allowed to be called multiple times without causing problems. Doesn't that mean finalizers could be called multiple times without causing problems?Queston
@DavidKlempfner see ObjectDisposedExceptionAlkalosis
R
35

From Jon Jagger:

"A struct cannot have a destructor. A destructor is just an override of object.Finalize in disguise, and structs, being value types, are not subject to garbage collection."

Reluct answered 26/11, 2011 at 3:38 Comment(6)
Short and to the point. Yours is also the only answer that doesn't erroneously mention the stack.Gonad
Why not have the finalizer run when the struct goes out of scope if it's a local variable, or, when it's part of an object and the object is garbage collected? If a struct can implement IDisposable, why can't it have a finalizer?Queston
Aren't structs garbage collected if they are contained in an object which would be on the heap?Queston
@Backwards_Dave no. Objects on the heap are GCed. If you have an instance of class C { int x; } you don't say "integer x gets GCed".Amargo
@Amargo what happens to the int that is on the heap when its object its contained within is GCed?Queston
It's a bit pedantic haha, GCing applies to whole GC allocations. An individual byte within the GC allocation isn't individually freed, the GC allocation is freed. If you read here for example github.com/dotnet/docs/blob/main/docs/standard/… garbage collection applies to whole objects. Sorta like how if you do X = malloc(sizeof(someStruct)) then free(X), you aren't said to be freeing an individual field F within X; you're freeing the block of memory X or alternatively, the block of memory containing F known as X; it's weird to say you're freeing F :PAmargo
P
1

Every object other than arrays and strings is stored on the heap in the same way: a header which gives information about the "object-related" properties (its type, whether it's used by any active monitor locks, whether it has a non-suppressed Finalize method, etc.), and its data (meaning the contents of all the type's instance fields (public, private, and protected intermixed, with base-class fields appearing before derived-type fields). Because every heap object has a header, the system can take a reference to any object and know what it is, and what the garbage-collector is supposed to do with it. If the system has a list of all objects which have been created and have a Finalize method, it can examine every object in the list, see if its Finalize method is unsuppressed, and act on it appropriately.

Structs are stored without any header; a struct like Point with two integer fields is simply stored as two integers. While it is possible to have a ref to a struct (such a thing is created when a struct is passed as a ref parameter), the code that uses the ref has to know what type of struct the ref points to, since neither the ref nor the struct itself holds that information. Further, heap objects may only be created by the garbage-collector, which will guarantee that any object which is created will always exist until the next GC cycle. By contrast, user code can create and destroy structs by itself (often on the stack); if code creates a struct along with a ref to it, and passes that ref it to a called routine, there's no way that code can destroy the struct (or do anything at all, for that matter) until the called routine returns, so the struct is guaranteed to exist at least until the called routine exits. On the other hand, once the called routine exits, the ref it was given should be presumed invalid, since the caller would be free to destroy the struct at any time thereafter.

Pacifist answered 28/6, 2012 at 15:34 Comment(0)
P
-4

Becuase by definition destructors are used to destruct instances of classes, and structs are value types.

Ref: http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

By Microsoft's own words: "Destructors are used to destruct instances of classes." so it's a little silly to ask "Why can't you use a destructor on (something that is not a class)?" ^^

Premeditation answered 26/11, 2011 at 5:25 Comment(7)
well this type of "by definition road" is pedantic but I am not sure its best line to take on interview. However I can see merit to this approach in some cases. When we are asked "why parallel lines do not cross?" answer "by definition" seems much more appropriate than here I believe.Hulk
I agree wording it "by definition" is good so I made the tweek :)Premeditation
problem here is : many questions can be answered this way, however a better answer often also exists. And while it will be a totally correct answer, it won't get us points in most cases.Hulk
Destructors don't destruct instances of classes. Their role, despite their unfortunate name, is allow classes to carry out actions which need to be carried out, and which they alone are capable of doing. For example, if "someone" asks the system to open a file, the system will create a file handle and give it to the requester with promise that those with the handle may have exclusive access to the file until someone with the handle says such access is no longer required. A "File" class which holds such a handle would have a duty to ensure that the system is told when it's no longer needed.Pacifist
Otherwise, the system would have no way of knowing that nobody still held a copy of the file handle, and would thus keep the file opened indefinitely, for the exclusive for an entity that no longer exists. Ironically, destructors don't destroy classes, but rather delay their destruction until all their affairs have been put in order.Pacifist
supercat, thanks for the added info. So you're saying that Microsoft is wrong in their own definition? (It wouldn't shock me, to be honest).Premeditation
There are a lot of recommendations on Microsoft's web site which may have had value once upon a time, but nowadays are far from the best way of doing things. Finalize was a much more descriptive name than cleanup, though NotifyOfAbandonment would be even better.Pacifist

© 2022 - 2024 — McMap. All rights reserved.