views:

511

answers:

1

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?

+10  A: 

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?

Eric Lippert
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?
Secret Squirrel
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.
Eric Lippert
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.
Eric Lippert
I'm not specifically trying to write portable C# code, but I am looking for best practice in relation to destructors.
Secret Squirrel