Instance variable initializers - C# vs. VB.NET
I ran into an interesting issue today when changing some VB.NET code. I was surprised at the outcome so I did a quick repro case in C# and didn't have a problem.
Given this simple C# app, the results are pretty easy to predict:
using System; namespace InheritIssue { class Class1 { [STAThread] static void Main(string[] args) { Foo f = new Foo(); } } public class Base { private int _data; public Base() { Init(); } protected virtual void Init() { _data = 1; Console.WriteLine("Data initialized to " + _data); } } public class Foo : Base { private Alpha a = new Alpha(); public Foo() : base() { Console.WriteLine("hello"); } protected override void Init() { base.Init(); Console.WriteLine("Initial Age: " + a.Age); } } public class Alpha { private int _age = -1; public int Age { get { return _age; } set { _age = value; } } } }
In case you don't feel like running the code, you'll get:
Data initialized to 1
Initial Age: -1
hello
Now the same code in VB.NET:
Option Strict On Option Explicit On Namespace InheritIssue Public Class Class1 <STAThread()> _ Shared Sub Main() Dim f As Foo = New Foo End Sub Public Class Base Private _data As Integer Public Sub New() Init() End Sub Protected Overridable Sub Init() _data = 1 Console.WriteLine("Data initialized to " & _data) End Sub End Class Public Class Foo Inherits Base Private a As Alpha = New Alpha Public Sub New() MyBase.New() Console.WriteLine("hello") End Sub Protected Overrides Sub Init() MyBase.Init() Console.WriteLine("Initial Age: " & a.Age) End Sub End Class Public Class Alpha Private _age As Integer = -1 Public Property Age() As Integer Get Return _age End Get Set(ByVal Value As Integer) _age = Value End Set End Property End Class End Class End Namespace
This code gets to the Base class initializer and outputs "Data initialized to 1". It then throws a NullReferenceException trying to access the Age property of the "a" object. The "a" variable has not been initialized and is still null (Nothing). I thought this was odd so I checked out the IL for Foo's constructor in both the C# and VB.NET version and saw something noticeably different.
In C#, the ctor is:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 28 (0x1c) .maxstack 2 IL_0000: ldarg.0 IL_0001: newobj instance void InheritIssue.Alpha::.ctor() IL_0006: stfld class InheritIssue.Alpha InheritIssue.Foo::a IL_000b: ldarg.0 IL_000c: call instance void InheritIssue.Base::.ctor() IL_0011: ldstr "hello" IL_0016: call void [mscorlib]System.Console::WriteLine(string) IL_001b: ret } // end of method Foo::.ctor
In VB.NET, there's an ever-so-slight difference:
.method public specialname rtspecialname instance void .ctor() cil managed { // Code size 28 (0x1c) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void InheritIssue.Class1/Base::.ctor() IL_0006: ldarg.0 IL_0007: newobj instance void InheritIssue.Class1/Alpha::.ctor() IL_000c: stfld class InheritIssue.Class1/Alpha InheritIssue.Class1/Foo::a IL_0011: ldstr "hello" IL_0016: call void [mscorlib]System.Console::WriteLine(string) IL_001b: ret } // end of method Foo::.ctor
C# initialized the "a" variable to a new instance of Alpha before calling the base ctor. VB.NET did not do this. So when the Init method is called in Foo(), "a" is still null in the VB.NET version and you get a NullReferenceException. I checked the Visual Basic Language Specification for instance constructors and it states:
When a constructor's first statement is of the form MyBase.New(...), the constructor implicitly performs the initializations specified by the variable initializers of the instance variables declared in the type. This corresponds to a sequence of assignments that are executed immediately after invoking the direct base type constructor. Such ordering ensures that all base instance variables are initialized by their variable initializers before any statements that have access to the instance are executed.
Emphasis added by me. Now checking the C# spec:
When an instance constructor has no constructor initializer, or it has a constructor initializer of the form base(...), that constructor implicitly performs the initializations specified by the variable-initializers of the instance fields declared in its class. This corresponds to a sequence of assignments that are executed immediately upon entry to the constructor and before the implicit invocation of the direct base class constructor. The variable initializers are executed in the textual order in which they appear in the class declaration.
Whoa! Important difference there (very different!). I wonder why the language designers chose such a different path on deciding when to intialize instance variables?