Может ли VB.NET принудительно инициализировать переменные экземпляра перед вызовом конструктора базового типа?

После отладки особенно сложной проблемы в VB.NET с участием порядка, в котором инициализируются переменные экземпляра, я обнаружил, что существует разрывное несоответствие между поведением, которое я ожидал от С#, и фактическим поведением в VB.NET.

Nota bene: Этот вопрос касается небольшого несоответствия в поведении VB.NET и С#. Если вы являетесь фанатом языка, который не может предоставить ответ, отличный от того, "почему вы должны использовать С#, noob", вам здесь нечего видеть; добро пожаловать.

В частности, я ожидал поведения, описанного в С# Language Specification (выделено мной):

Когда конструктор экземпляра не имеет инициализатора конструктора или имеет инициализатор конструктора формы base(...), этот конструктор неявно выполняет инициализации, указанные инициализаторами переменных полей экземпляров, объявленных в его классе. Это соответствует последовательности назначений, которые выполняются сразу после входа в конструктор и перед неявным вызовом конструктора прямого базового класса. Инициализаторы переменных выполняются в текстовом порядке, в котором они отображаются в объявление класса.

Сравните это с частью Спецификации языка VB.NET, относящейся к Instance Constructors, в которой говорится (выделено мной):

Когда первый оператор конструктора имеет вид MyBase.New(...), конструктор неявно выполняет инициализации, указанные инициализаторами переменных переменных экземпляра, объявленных в типе. Это соответствует последовательности назначений, которые выполняются сразу после вызова конструктора прямого базового типа. Такое упорядочение гарантирует, что все базовые переменные экземпляра инициализируются их инициализаторами переменных до того, как все операторы, имеющие доступ к экземпляру, будут выполняется.

Несоответствие здесь сразу становится очевидным. С# инициализирует переменные уровня класса перед вызовом базового конструктора. VB.NET делает именно обратное, по-видимому, предпочитая вызывать базовый конструктор перед установкой значений полей экземпляра.

Если вы хотите увидеть какой-то код, этот связанный вопрос дает более конкретный пример расходящегося поведения. К сожалению, он не дает никаких указаний о том, как можно заставить VB.NET следовать модели, установленной С#.

Меня меньше интересует, почему дизайнеры двух языков выбрали такие расходящиеся подходы, как я в возможных обходных решениях этой проблемы. В конечном счете, мой вопрос следующий: Есть ли способ, которым я могу написать или структурировать свой код в VB.NET, чтобы принудительно инициализировать переменные экземпляра до того, как конструктор базового типа называется, а также стандартный поведение в С#?

Ответ 1

Если у вас есть виртуальные участники, которые будут вызваны во время строительства (против лучших советов, но мы уже это согласовали), вам нужно переместить вашу инициализацию в отдельный метод, который может защитить себя от нескольких вызовов (т.е. если init уже произошел, немедленно вернитесь). Затем этот метод будет вызываться виртуальными членами и вашим конструктором, прежде чем они будут полагаться на произошедшую инициализацию.

Это немного беспорядочно, и может представлять собой незначительное снижение производительности, но в VB вы еще ничего не можете сделать.

Ответ 2

Любой хорошо написанный класс должен либо гарантировать, что любые виртуальные члены, которые могут быть вызваны на частично сконструированном экземпляре, будут вести себя разумно. С# считает философию, что экземпляр класса, чьи инициализаторы полей были запущены, будет находиться в достаточно разумном состоянии, чтобы позволить использовать виртуальные методы. VB.net придерживается философии, позволяющей инициализаторам поля использовать частично сконструированный объект (и - с небольшой работой - любые параметры, переданные конструктору), более полезен, чем гарантия того, что инициализаторы полей будут выполняться до того, как виртуальные методы называются.

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

Class ParamPasserBase(Of T) ' Generic class for passing one constructor parameter
  Protected ConstructorParam1 As T
  Sub New(Param As T)
    ConstructorParam1 = Param
  End SUb
End Class
Class MyThing
  Inherits ParamPasserBase(Of Integer)
  Dim MyArray(ConstructorParam1-1) As String
  Sub New(ArraySize As Integer)
    MyBase.New(ArraySize)
  End Sub
  ...
End Class

В С# нет отличного способа, чтобы декларации полей или инициализаторы использовали аргументы, переданные конструктору. В vb это можно сделать достаточно чисто, как показано выше. Обратите внимание, что в vb также можно использовать параметр конструктора by-reference для контрабанды копии строящегося объекта до запуска любых инициализаторов полей; если класс Dispose правильно написан для обработки частично сконструированных объектов, объект, который бросает его конструктор, может быть правильно очищен.