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?

3 Comments

  • I ran into this ac ouple of years ago and was quite surprised to find out the way the 2 languages initialize are different. Made translating some code from one to the other a bit tricky. The oriignal code worked fine but the translated code failed. So after stepping through both I could see the issue. Your description is much better though.

  • Sorry, only read this blog post now.



    This problem has also caught me a few times in the past. I thought the general rule was not to call a virtual method from within a class.



  • Good One Man keep it up

Comments have been disabled for this content.