Что на самом деле означает "деструкторы не наследуются"?

Раздел 10.13, Деструкторы спецификации языка C# 3.0 гласит следующее:

Деструкторы не наследуются. Таким образом, класс не имеет деструкторов, кроме того, который может быть объявлен в этом классе.

Раздел "Деструкторы" Руководства по программированию в C# содержит пример, демонстрирующий, как вызываются деструкторы в иерархии наследования, включая следующий оператор:

... деструкторы для... классов вызываются автоматически и в порядке от самого производного к наименее производному.

Я исследовал это на различных практических примерах, включая пример с базовым классом, который определяет деструктор, с производным классом, который наследуется от базового класса и не определяет деструктор. Создание экземпляра производного класса, позволяя всем ссылкам на экземпляр выйти из области видимости, а затем форсировать сборку мусора, демонстрирует, что деструктор, определенный в базовом классе, вызывается, когда экземпляр производного класса завершается.

Мой вопрос заключается в том, что на самом деле означает "деструкторы не наследуются", поскольку, хотя вы не можете явно вызывать деструктор, деструкторы в цепочке наследования вызываются автоматически, а деструкторы базового класса вызываются, даже если производный класс не определяет деструктор.?

Связано ли это с каким-то тонким семантическим различием в том, что финализация выполняется сборщиком мусора, а не языком / компилятором C#?

Изменить 1:

В то время как спецификация языка C# также утверждает, что "конструкторы экземпляров не наследуются", поведение по отношению к конструкторам значительно отличается от десктрукторов и лучше соответствует IMO с терминологией "не унаследовано", как показано в примере ниже:

  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"); 

Поведение по отношению к деструкторам очень отличается от этого, как показано в следующем примере, который расширяет предыдущий пример конструктора путем добавления десктруктора только к базовому классу.

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

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

  ...

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

Приведенный выше пример демонстрирует, что конструкторы базового класса будут вызываться, когда экземпляр производного класса будет завершен, даже если производный класс явно не определяет деструктор.

Я просто хочу сказать, что я сталкивался со многими людьми, которые не понимают или не понимают, что это происходит, и значительная часть причины этого - утверждение "деструкторы не наследуются".

Изменить 2:

В спецификации языка C# также указано следующее и приведен пример кода внутренней реализации:

Деструкторы реализуются путем переопределения виртуального метода Finalize в System.Object. Программы на C# не могут переопределять этот метод или вызывать его (или переопределяют) напрямую.

Поскольку внутренняя реализация, на самом деле, основана на наследовании, как указано выше, я думаю, что мой вопрос верен, и я не думаю, что какие-либо ответы, которые я получил до сих пор, правильно ответили на вопрос - Что означает "деструкторы не наследуются" на самом деле?

1 ответ

Решение

Это не вопрос победы. Я изо всех сил стараюсь понять ваши вопросы, но вы, похоже, озабочены защитой от совершенно воображаемых атак.

Оставляя это в стороне и рассматривая ваши вопросы по одному:

Мой вопрос заключается в том, что на самом деле означает "деструкторы не наследуются", поскольку, хотя вы не можете явно вызывать деструктор, деструкторы в цепочке наследования вызываются автоматически, а деструкторы базового класса вызываются, даже если производный класс не определяет деструктор.?

Это фактически означает, что деструкторы базового класса не являются членами производного класса.

Мой встречный вопрос: "Почему вы считаете, что факты (1) вы не можете вызывать деструктор напрямую, (2) деструкторы в цепочке наследования вызываются автоматически при сборе, и (3) деструкторы базового класса вызываются, даже если Производный класс не определяет конструктор, имеет какое-либо отношение к вопросу о том, является ли деструктор базового класса членом производного класса?

Связано ли это с каким-то тонким семантическим различием в том, что финализация выполняется сборщиком мусора, а не языком / компилятором C#?

Нету.

Язык C# является спецификацией; это ничего не реализует. Компилятор C# реализует спецификацию; это, конечно, не "осуществить завершение". CLR - это то, что реализует завершение.

Независимо от всего этого, тот факт, что деструкторы в базовом классе не являются членами производного класса, не имеет никакого отношения к тому, как финализация реализована сборщиком мусора CLR.

Мы вернемся к вопросу о семантике финализации CLR и к тому, почему они не имеют отношения к вопросу наследования.

В то время как спецификация языка C# также утверждает, что "конструкторы экземпляров не наследуются", поведение по отношению к конструкторам значительно отличается от десктрукторов и лучше соответствует IMO с термином "не наследуется"

Хорошо, я принимаю, что вы в это верите. Почему вы считаете, что такое поведение имеет отношение к вопросу наследования? Я ни в малейшей степени не понимаю, почему вы в это верите. Для наследования важно то, является ли рассматриваемая сущность членом производного типа просто потому, что она является членом базового типа.

Я согласен с тем, что тот факт, что конструктор для базового типа не может быть вызван напрямую через конструкцию производного типа, в котором отсутствует этот конструктор, согласуется с тем фактом, что конструкторы не наследуются. Я просто не понимаю, как это относится к вопросу о том, наследуются ли деструкторы.

Более уместным является тот факт, что базовый класс с открытым конструктором без параметров DOES вызывает этот конструктор, когда производный класс, в котором отсутствует открытый конструктор без параметров, создается с его конструктором по умолчанию. Производный класс не наследует открытый конструктор без параметров базового класса, но, тем не менее, он вызывается. Если вы принимаете, что конструктор, не унаследованный, может быть вызван таким образом, то почему бы не принять, что деструктор также вызывается с похожей семантикой?

Я просто хочу сказать, что я сталкивался со многими людьми, которые не понимают или не понимают, что это происходит, и значительная часть причины этого - утверждение "деструкторы не наследуются".

Я полностью согласен. Вероятно, не следует писать деструкторы, если у вас нет четкого понимания того, что такое правильная семантика. Я встревожен количеством книг для начинающих C#, которые рассматривают деструкторы как подходящую тему для новичков; Написание правильного деструктора - одна из самых сложных основных задач в C#.

Поскольку внутренняя реализация фактически основана на наследовании, как указано выше, я думаю, что мой вопрос верен

Ваш вопрос совершенно верный независимо. Но теперь вы связываете детали реализации функции со спецификацией функции.

По аналогии рассмотрим анонимные типы. Достаточно разумно, у них нет имен. Но метаданные, которые мы плюем, конечно же, называют тип; CLR требует, чтобы у типов были имена. Это противоречие? На самом деле, нет. Концептуальная сущность, называемая в спецификации "анонимным типом", не имеет имени, и это имеет значение. Разумеется, разработчик может свободно использовать платформу, которая не требует именования всех типов.

Точно так же наша реализация C# реализует деструкторы, выплевывая IL в метод, который переопределяет виртуальный метод, называемый Finalize. Язык C# был тщательно спроектирован, чтобы не зависеть от этой функции среды выполнения. Другая реализация C# совершенно свободна в выборе другого механизма для реализации деструкторов. (Для нас может быть хорошей идеей изменить спецификацию, чтобы было более понятно, что немного о том, как реализованы деструкторы, является информативной деталью реализации, а не требованием языка C#.)

Но независимо от выбора деталей реализации деструкторы в базовом классе не являются членами производного класса. Так же, как независимо от деталей реализации, анонимные типы не имеют имен.

Теперь понятно, или у вас есть еще вопросы?

Другие вопросы по тегам