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?