What happens when you change a struct to a class in .NET if you don’t recompile the type’s dependencies?

An interesting issue arose out of a recent code review I was conducting.  If you change a type from a struct to a class, do you have to recompile all modules that are dependent on that type?

Let’s use a simple example.  The following diagram illustrates several packages that are dependent on a “Geometry” package, with the Geometry package containing a struct called “Point”:

Module_Dependency_Example

Point is defined as follows:

   1: public struct Point
   2: {
   3:     public int X;
   4:     public int Y;
   5: }

In the dependent module, I’ve defined a class called “Consumer” and created a constructor that references the Point struct:

   1: public class Consumer
   2: {
   3:     public Consumer()
   4:     {
   5:         var point = new Point
   6:         {
   7:             X = 1,
   8:             Y = 5
   9:         };
  10:     }
  11: }

Let’s examine this constructor in IL:

   1: .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
   2: {
   3:     .maxstack 2
   4:     .locals init (
   5:         [0] valuetype [DependentModule]Geometry.Point point,
   6:         [1] valuetype [DependentModule]Geometry.Point <>g__initLocal0,
   7:         [2] valuetype [DependentModule]DependentModule.Point CS$0$0000)
   8:     L_0000: ldarg.0 
   9:     L_0001: call instance void [mscorlib]System.Object::.ctor()
  10:     L_0006: nop 
  11:     L_0007: nop 
  12:     L_0008: ldloca.s CS$0$0000
  13:     L_000a: initobj [ExampleModule]DependentModule.Point
  14:     L_0010: ldloc.2 
  15:     L_0011: stloc.1 
  16:     L_0012: ldloca.s <>g__initLocal0
  17:     L_0014: ldc.i4.1 
  18:     L_0015: stfld int32 [ExampleModule]DependentModule.Point::X
  19:     L_001a: ldloca.s <>g__initLocal0
  20:     L_001c: ldc.i4.5 
  21:     L_001d: stfld int32 [ExampleModule]DependentModule.Point::Y
  22:     L_0022: ldloc.1 
  23:     L_0023: stloc.0 
  24:     L_0024: nop 
  25:     L_0025: ret 
  26: }

Pay attention to lines 5 through 7; the “valuetype” instruction declares an instance variable as a value type.  This is important, because it means that the memory that the variable “point” occupies will be passed by value, not by reference.  Also notice line 13; this call’s the Point struct’s constructor.

If the Point struct changes to a class, the IL in lines 5 through 7 will throw an exception, because classes can’t be declared as value types.  If we change, the Point struct to a class and recompile the Geometry and dependent modules, the constructor’s init block looks like this:

   4: .locals init (
   5:     [0] class [ExampleModule]DependentModule.Point point,
   6:     [1] class [ExampleModule]DependentModule.Point <>g__initLocal0)

Notice how the point variable is now being defined with the “class” instruction, telling the native-compiler to treat this variable as a reference type.  Also, the call to initialize the variable has changed:

   13: L_0008: newobj instance void [DependentModule]Geometry.Point::.ctor()

Instead of using the “initobj” instruction, the “newobj” instruction is being used.

The impact of this nuance is very small, because everybody is either using a continuous-integration server to build their solutions (I hope).  However, when you’re attempting to perform local changes on your development box this problem can manifest itself.

  1. gravatar

    # by Nhà Xinh - April 21, 2015 at 10:32 AM

    Nếu bạn đang muốn đăng tin bán nhà hay bán đất hoặc bạn muốn ban nha quan 12 , nha dat xinh thì hãy đến với chúng tôi rao vat mien phi, với chất lương hàng đầu chúng tôi sẽ giúp các bạn , đăng tin và xem các khu vực nha dat go vap, nha dat quan 9, nha dat thu duc , nha dat binh tan , nha dat tan phu , nha dat tan binh và các khu vực khác trên NguyenManhKha toàn quốc với uy tín và hiệu quả cao khi bạn đến với chúng tôi.