Constructors and Value Types [Ron Petrusha]

Constructors and Value Types [Ron Petrusha]

  • Comments 7

A number of recent customer comments have made it clear that some confusion surrounds the use of constructors with value types. Most common are the complaints that we've failed to document a constructor for a particular value type. In one case, though, a customer asked that we remove the documentation on constructors for value types, since he or she was certain that they are not supported by the .NET Framework.

Just as you can define parameterized constructors for reference types, you can define parameterized constructors for value types. For example, the following code defines a VehicleInfo structure that has two parameters: the number of wheels on a vehicle, and its number of doors.

[Visual Basic]

Public Structure VehicleInfo

   Private nWheels As Integer

   Private nDoors As Integer

 

   Public Sub New(ByVal wheels As Integer, ByVal doors As Integer)

      nWheels = wheels

      nDoors = doors

   End Sub

 

   Public ReadOnly Property NumberOfWheels() As Integer

      Get

         Return nWheels

      End Get

   End Property

 

   Public ReadOnly Property Doors() As Integer

      Get

         Return nDoors

      End Get

   End Property

End Structure

 

[C#]

using System;

 

public struct VehicleInfo

{

   private int nWheels;

   private int nDoors;

 

   public VehicleInfo(int wheels, int doors)

   {

      nWheels = wheels;

      nDoors = doors;

   }

 

   public int NumberOfWheels

   {

      get { return nWheels; }

   }

 

   public int Doors

   {

      get { return nDoors; }

   }

}

The example compiles normally, and when we use IL DASM to examine our type, we can see that it includes the parameterized constructor.

Constructors and Value Types in ILDASM

So far, we haven't observed any differences between defining constructors for value types and defining them for reference types. However, this is where the similarity ends. While we can define a default or parameterless constructor for reference types, the attempt to define a default constructor for value types produces a compiler error. For example, if we modify the previous code by replacing the parameterized constructor with a default constructor and adding setters for the structure's two properties, the Visual Basic compiler displays the following error message:

error BC30629: Structures cannot declare a non-shared 'Sub New' with no parameters.

The equivalent C# code also fails to compile, with a slightly different error message:

error CS0568: Structs cannot contain explicit parameterless constructors

Not only can we not define a parameterless constructor for value types, but compilers don't supply one automatically if there is no defined constructor, as they do for reference types. For example, if we again modify our source code by deleting the parameterless constructor and then successfully compile, we can again use IL DASM to inspect the members of our type. As the following figure shows, a parameterless constructor is not a member of the structure.

 

Constructors and Value Types in ILDASM

 

So to summarize, there are two major differences in the definition of constructors between value types and reference types:

  • You can define a parameterless constructor for a reference type. You cannot define one for a value type.
  • If you do not define any constructors, the compiler supplies a parameterless constructor for a reference type. It does not supply one for a value type.

Given this, it may seem surprising that if we attempt to instantiate a VehicleInfo object by calling its parameterless constructor, as the following example does, our code compiles successfully.

[Visual Basic]

Module Example

   Public Sub Main()

      Dim bicycle As New VehicleInfo()

      bicycle.Doors = 0

      bicycle.NumberOfWheels = 2

   End Sub

End Module

 

[C#]

using System;

 

public class Example

{

   public static void Main()

   {

      VehicleInfo bicycle = new VehicleInfo();

      bicycle.Doors = 0;

      bicycle.NumberOfWheels = 2;

   }

}

On the one hand, no parameterless constructor exists in our VehicleInfo type. On the other hand, we apparently are able to call this non-existent constructor. How can we explain this incongruous behavior?

Let's begin by using IL DASM to examine the MSIL for a "normal" call to a parameterless constructor from a class. For the purpose of the example, we can define a class with no members named TypicalClass and then instantiate it from another class that contains Main, the application entry point. The following is the MSIL emitted by a Visual Basic compiler for the Main method. Note the explicit call to the TypicalClass default constructor. (A C# compiler also emits an identical call to the TypicalClass default constructor.)

.method public static void  Main() cil managed

{

  .entrypoint

  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )

  // Code size       7 (0x7)

  .maxstack  1

  .locals init (class TypicalClass V_0)

  IL_0000:  newobj     instance void TypicalClass::.ctor()

  IL_0005:  stloc.0

  IL_0006:  ret

} // end of method Example::Main

We can then compare this with the IL generated by our call to the non-existent VehicleInfo parameterless constructor. The following is the IL emitted by the C# compiler, although for our purposes the IL emitted by the Visual Basic compiler is identical.

.method public hidebysig static void  Main() cil managed

{

  .entrypoint

  // Code size       28 (0x1c)

  .maxstack  2

  .locals init (valuetype [NoConstructor1]VehicleInfo V_0)

  IL_0000:  nop

  IL_0001:  ldloca.s   V_0

  IL_0003:  initobj    [NoConstructor1]VehicleInfo

  IL_0009:  ldloca.s   V_0

  IL_000b:  ldc.i4.0

  IL_000c:  call       instance void [NoConstructor1]VehicleInfo::set_Doors(int32)

  IL_0011:  nop

  IL_0012:  ldloca.s   V_0

  IL_0014:  ldc.i4.2

  IL_0015:  call       instance void [NoConstructor1]VehicleInfo::set_NumberOfWheels(int32)

  IL_001a:  nop

  IL_001b:  ret

} // end of method Example::Main

The call to the non-existent parameterless constructor in our source code has been replaced by a call to Initobj, an MSIL opcode that is documented at http://msdn.microsoft.com/en-us/library/system.reflection.emit.opcodes.initobj(VS.100).aspx. Initobj initializes each field in a value type to its default value.

This feature - the ability to instantiate a value type by using a language's constructor syntax to invoke a default value type even though one does not exist -- is known as the implicit default constructor. In C# in particular, it is necessary to use the implicit default constructor to instantiate value types that do not define parameterized constructors, since otherwise the C# compiler generates error CS0165: "Use of unassigned local variable 'bicycle'", because C# does not allow variables to be used before they're explicitly initialized.

On the other hand, Visual Basic does not  forbid the use of unassigned value types, since it provides a call to the implicit default constructor if none is present in source code. For example, the following Visual Basic code is similar to the previous example, except that it omits the call to the VehicleInfo default constructor.

[Visual Basic]

Module Example

   Public Sub Main()

      Dim bicycle As VehicleInfo

      bicycle.Doors = 0

      bicycle.NumberOfWheels = 2

   End Sub

End Module

The following is the MSIL emitted by a Visual Basic compiler for this version of the Main method.

.method public static void  Main() cil managed

{

  .entrypoint

  .custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )

  // Code size       17 (0x11)

  .maxstack  2

  .locals init (valuetype [NoConstructor1]VehicleInfo V_0)

  IL_0000:  ldloca.s   V_0

  IL_0002:  ldc.i4.0

  IL_0003:  call       instance void [NoConstructor1]VehicleInfo::set_Doors(int32)

  IL_0008:  ldloca.s   V_0

  IL_000a:  ldc.i4.2

  IL_000b:  call       instance void [NoConstructor1]VehicleInfo::set_NumberOfWheels(int32)

  IL_0010:  ret

} // end of method Example::Main

In this case, the Initobj opcode has been replaced by init, but the result is the same: each field of a value type is initialized to its default value. In Visual Basic, an implicit default constructor is invoked whether or not that constructor is actually used in source code.

  • hHanks for showing some samples, although most of it seems pretty logical once you know that "every struct has a default, non-overridable parameterless constructor".

    Could you tell us why this design decision was made and what are the (dis)advantages? I suspect it's got something to do with interop and non-nullability but I'd love to see a post about it.

  • I believe the reason you can't override a struct's paramless "constructor" is as follows:

    From a class (or an another struct) you can reference a value type. Referencing a value type allocates memory and initializes it. The "constructor" (which is not there) shouldn't be able to cause any side effects like calling a database, or doing some lengthy calculation etc. etc.

    If CLR wouldn't prevent this you could end up with a program that's in a deadlock situation even before your Main method has started!

  • @Jaap: the same problem exists also for static field initializers (of any type), but they are legal.

  • Just like Banjobeni's question, actually I'm curious why it's designed that way, the rational, the trade-off, the advantages and disadvantages, etc.

    Thanks.

  • The story is actually rather more complicated than this...

    CLR itself does not prohibit defining parameterless constructors for valuetypes. E.g. the following IL is correct and verifiable:

       .class public sealed Foo extends [mscorlib]System.ValueType

       {

           .field public int32 Bar

           .method public specialname rtspecialname instance void .ctor()

           {

               ldarg.0

               ldc.i4 123

               stfld int32 Foo::Bar

               ret

           }

       }

    Furthermore, if using operator new with such a type, C# and VB will both note the parameterless constructor, and invoke it!

       class Program

       {

           static void Main()

           {

               var foo = new Foo();

               Console.WriteLine(foo.Bar); // 123!

           }

       }

    However, using default() in C#, or Nothing in VB, will still allow you to "null-initialize" the instance, circumventing that default constructor:

       class Program

       {

           static void Main()

           {

               var foo = default(Foo);

               Console.WriteLine(foo.Bar); // 0

           }

       }

    Furthermore, in any case where the variable is initialized implicitly - e.g. when it's a field in a class and undergoes default initialization, or when it's an array element - the parameterless constructor won't be called, either.

    In fact, I believe that it is that latter bit is precisely why both C# and VB won't let you write such structs, even if they try to use them correctly. Value types in CLR are supposed to be usable with no constructor being called on them, and copyable by direct blitting. Thus, even if a parameterless constructor is present, there's no guarantee that it will be used in all scenarios - sometimes you just get "null-initialization" - which is confusing for people coming from C++ background. Much easier to restrict the whole thing altogether on language leve.

  • I think it's worth noting that it is possible to define a parameterless constructor for a value type in IL just not through the languages.  But such a constructor will not be called in all of the places you would expect.

  • it is rich article

    many thanks

Page 1 of 1 (7 items)