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

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

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

В частности, я ожидал поведение, описанное в спецификации языка C# (выделено):

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

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

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

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

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

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

2 ответа

Решение

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

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

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

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

Class ParamPasserBase (Of T) 'Общий класс для передачи одного параметра конструктора
  Защищенный ConstructorParam1 As T
  Sub New(Param As T)
    ConstructorParam1 = Param
  Конец SUb
Конечный класс
Класс MyThing
  Наследует ParamPasserBase(Of Integer)
  Dim MyArray(ConstructorParam1-1) как строка
  Sub New(ArraySize As Integer)
    MyBase.New(ArraySize)
  End Sub
  ...
Конечный класс

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

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