C# - What does "destructors are not inherited" actually mean?
Asked Answered
S

1

13

Section 10.13, Destructors, of the C# Language Specification 3.0 states the following:

Destructors are not inherited. Thus, a class has no destructors other than the one which may be declared in that class.

The Destructors section of the C# Programming Guide contains an example demonstrating how destructors in an inheritance hierarchy are called, including the following statement:

...the destructors for the ... classes are called automatically, and in order, from the most-derived to the least-derived.

I have investigated this with various practical examples, including one with a base class that defines a destructor, with a derived class that inherits from the base class and does not define a destructor. Creating an instance of the derived class, allowing all references to the instance to go out of scope and then forcing a garbage collection demonstrates that the destructor defined in the base class is called when the instance of the derived class is finalized.

My question is what does "destructors are not inherited" actually mean, since although you can't call a destructor explicitly, destructors in an inheritance chain are called automatically, and base class destructors are called even if the derived class does not define a destructor?

Does it relate to some subtle semantic distinction that finalization is implemented by the garbage collector rather than the C# language/compiler?

Edit 1:

While the C# language spec also states that "instance constructors are not inherited", the behaviour in relation to constructors is significantly different from desctructors, and fits better IMO with the "not inherited" terminology, as demonstrated in the example below:

  public class ConstructorTestBase
  {
    public ConstructorTestBase(string exampleParam)
    {
    }
  }

  public class ConstructorTest: ConstructorTestBase
  {
    public ConstructorTest(int testParam)
      : base(string.Empty)
    {
    }
  }

  ...

  // The following is valid since there is a derived class constructor defined that
  // accepts an integer parmameter.
  ConstructorTest test1 = new ConstructorTest(5);

  // The following is not valid since the base class constructor is not inherited
  // by the derived class and the derived class does not define a constructor that
  // takes a string parameter.
  ConstructorTest test2 = new ConstructorTest("Test"); 

The behaviour in relation to destructors is very different from this, as demonstrated in the following example, which extends the previous constructor example by adding a desctructor only to the base class.

  public class ConstructorTestBase
  {
    public ConstructorTestBase(string exampleParam)
    {
    }

    ~ConstructorTestBase()
    {
      Console.WriteLine("~ConstructorTestBase()");
    }
  }

  ...

  ConstructorTest test1 = new ConstructorTest(5);
  test1 = null;
  GC.Collect();

The example above demonstrates that base class constructors will be called when an instance of a derived class is finalized, even if the derived class does not explicitly define a destructor.

My point is simply that I have encountered many people who do not realise or understand that this what happens, and a significant part of the reason for this is the "destructors are not inherited" statement.

Edit 2:

The C# language spec also states the following and gives a code example of the under-the-hood implementation:

Destructors are implemented by overriding the virtual method Finalize on System.Object. C# programs are not permitted to override this method or call it (or overrides of it) directly.

Since the under-the-hood implementation is, in fact, based on inheritance, as stated above, I think my question is valid and I don't think any of the responses I've received so far have addressed the question properly - What does "destructors are not inherited" actually mean?

Sassoon answered 8/12, 2009 at 22:52 Comment(13)
Well, constructors are not inherited either, and yet when constructing an object, sure enough every time a base constructor gets called. Is that also confusing to you? If not, then what is the difference in your mind between default constructors and destructors?Saying
Please do not confuse the low reputation rating of an infrequent SO user with "novice" or "idiot". I am a senior software engineer with a formal educational background in software engineering, over 15 years commercial software development experience using object-oriented languages, and an interest in what is going behind the scenes. The question relates to the use of terminology rather than a lack of understanding of what is happening.Sassoon
I'm not implying you're an idiot. I'm asking a clarifying question, and it is a reasonable question. It seems reasonable that if "destructors are not inherited" is confusing then "constructors are not inherited" is also confusing. If both are confusing then I can provide an answer that covers both. If one is confusing and the other is not, then I need to understand why you understand one but not the other. And if neither is confusing, then I don't see why you're asking the question.Saying
In any case, I think you'll find that you get better results if you don't take offense so easily when people take an interest in your question and ask follow-up questions to clarify.Saying
I don't think your original response paid sufficient heed to the question as posed, and I found your use of language condascending. I fully understand that clarifications may be required for the benefit of others who are interested. I am not confused about what happens, merely the description that is being applied to the behaviour, since in practice destructors behave, to all intents and purposes, as if they are inherited (albeit not directly callable), and are implemented under-the-hood as virtual methods.Sassoon
I think you'll find that your question is not answered here: blogs.msdn.com/ericlippert/archive/2008/02/20/…Saying
It is also, somewhat more conveniently not answered here ;-) I'll post back later with a link to my blog entry on how to respond to questions without answering them or providing helpful information, whilst simultaneously conveying that the questioner is "confused". I didn't specifically ask you to respond and just as it is difficult to provide a perfect answer, so it is difficult to ask a perfect question, but as your blog points out, a respectful tone helps.Sassoon
Well now I'm confused. If you understand clearly and precisely what "inherited" means then why are you asking the question "what does inheritance mean?" And if you're not confused about what "inheritance" means then you must be deliberately conflating a whole pile of things that are not at all relevant to inheritance (like the semantics and implementation details of destructors) with inheritance. Why would you do that?Saying
I give up. You win. The documentation says destructors are not inherited, but the implementation is based on overriding a virtual method, with base class implementations used in a derived class if not overridden, and inherited implementations called automatically if overridden. It couldn't be clearer!?Sassoon
@SecretSquirrel: Name dropping is usually not a good way to have a technical discussion but: you don't sound like you know who Eric Lippert is. Maybe you do know, and I'm misinterpreting your words?Flexion
John, please. I'm just this guy.Saying
Yeah, you're just this guy who's in a position to know about the C# spec and how it's implemented. That's relevant to the discussion.Flexion
I do know who Eric is, and that he knows the answer to my question, but my point was that I couldn't discern the answer from the original response, and I felt the question was reasonable. I only have documentation and observation to go on. Eric has now answered my question to my satisfaction, for which I am indeed grateful.Sassoon
S
19

It's not a question of winning. I'm trying my best to understand your questions, but you seem preoccupied with defending yourself against entirely imaginary attacks.

Leaving that aside, and considering your questions one at a time:

My question is what does "destructors are not inherited" actually mean, since although you can't call a destructor explicitly, destructors in an inheritance chain are called automatically, and base class destructors are called even if the derived class does not define a destructor?

It actually means that destructors of a base class are not members of a derived class.

My counter-question is "why do you believe that the facts that (1) you cannot call a destructor directly, (2) destructors in an inheritance chain are called automatically upon collection, and (3) base class destructors are called even if the derived class does not define a constructor, have any bearing whatsoever on the question of whether or not a destructor of a base class is a member of a derived class?

Does it relate to some subtle semantic distinction that finalization is implemented by the garbage collector rather than the C# language/compiler?

Nope.

The C# language is a specification; it implements nothing. The C# compiler implements the specification; it certainly does not "implement finalization". The CLR is what implements finalization.

Regardless of any of that, the fact that destructors in a base class are not members of a derived class has nothing whatsoever to do with how finalization is implemented by the CLR garbage collector.

We'll return to the question of CLR finalization semantics, and why they are not relevant to the question of inheritance, at the end.

While the C# language spec also states that "instance constructors are not inherited", the behaviour in relation to constructors is significantly different from desctructors, and fits better IMO with the "not inherited" terminology

OK, I accept that you believe that. Why do you believe that these behaviours are relevant to the question of inheritance? I'm not following in the slightest why you believe that. What's relevant to inheritance is whether the entity in question is a member of the derived type simply by virtue of it being a member of the base type.

I agree that the fact that a constructor for a base type cannot be invoked directly via a construction of a derived type which lacks that constructor is consistent with the fact that constructors are not inherited. I just don't see how this is germane to the question of whether destructors are inherited.

A more germane fact is that a base class with a public parameterless constructor DOES have that constructor invoked when a derived class which lacks a public parameterless constructor is constructed with its default constructor. The derived class does not inherit the base class's public parameterless constructor, but it is called nevertheless. If you accept that a constructor, not inherited, can be invoked in this way, then why not accept that a destructor is also invoked with similar semantics?

My point is simply that I have encountered many people who do not realise or understand that this what happens, and a significant part of the reason for this is the "destructors are not inherited" statement.

I agree completely. One probably ought not to write destructors if one does not have a clear understanding of exactly what the correct semantics are. I am dismayed by the number of books for C# beginners which treat destructors as a suitable topic for novices; writing a correct destructor is one of the hardest basic tasks in C#.

Since the under-the-hood implementation is, in fact, based on inheritance, as stated above, I think my question is valid

Your question is perfectly valid regardless. But now you are conflating the implementation details of a feature with the specification of the feature.

By analogy, consider anonymous types. Reasonably enough, they don't have names. But the metadata we spit of course names the type; the CLR requires types to have names. Is that a contradiction? Not really. The conceptual entity called "anonymous type" by the specification does not have a name, and that's what matters. An implementor is of course free to use a platform that does not require naming of all types.

Similarly, our implementation of C# implements destructors by spitting IL into a method that overrides a virtual method called Finalize. The C# language has been carefully designed to not take a dependency upon this feature of the runtime. Another implementation of C# is perfectly free to choose some other mechanism to implement destructors. (It might be a good idea for us to modify the spec to make it more clear that the bit about how destructors are implemented is an informative implementation detail, and not a requirement of the C# language.)

But regardless of the choice of implementation details, destructors in a base class are not members of a derived class. Just as, regardless of implementation details, anonymous types do not have names.

Is that now clear, or do you have more questions?

Saying answered 9/12, 2009 at 21:30 Comment(5)
Thank you for your very thorough response. The short answer appears to be "that destructors of a base class are not members of a derived class" and that other implementations of C# may implement destructors differently. This is very helpful. The clarification to the spec would probably save others some pain. To write portable C# code, is a destructor required in all classes derived from a base class that implements IDisposable, to ensure that unmanaged resources in the base class are released, rather than relying on a call in a base destructor to a virtual Dispose(bool disposing) method?Sassoon
The question makes a dangerous assumption; why are you attempting to write code which is portable across difference implementations of C# which releases an unmanaged resource? Surely releasing an unmanaged resource is by its nature an operation which is bound to a particular operating system. An implementation of C# is free to run destructors on any thread it chooses, run destructors late, and so on. It seems to me very dangerous to try to write such a destructor.Saying
That said: no. If the base class is correctly written (so that failure to explicitly dispose the resource disposes it in the finalizer) then that should be enough. The C# specification guarantees that base class destructors will run if the object is actually GC'd.Saying
I'm not specifically trying to write portable C# code, but I am looking for best practice in relation to destructors.Sassoon
Out of curiosity, if another implementation implemented finalization just like .Net except that the magic function was called CleanUp rather than Finalize, would Finalize be a legal method name and CleanUp not, or would any user-defined method called CleanUp have its name changed to something else so as not to conflict with the underlying implementation method?Sunstone

© 2022 - 2024 — McMap. All rights reserved.