C# Automatically Implemented Properties

Some time ago, we analysed how C# Properties are emitted by IL.  In this article we will extend our view by investigating how C# 3 (and later) Automatically Implemented Properties (or Automatic Properties) works.  But first, let’s see how these properties are used.

In order to use Automatic Properties, one needs only to declare the Property signature and name and the accessibility of the get and set methods.  For example:

  1. public int Age { get; set; } // get and set methods get the accessibility of the property being declared

The example above is equivalent to:

  1. private int age;
  2. public int Age
  3. {
  4.     public get { return this.age; }
  5.     public set { this.age = value; }
  6. }

If we want to make the setter private, then we would write:

  1. public int Age { get; private set; }

Automatic Properties are consumed like their Parameterless Properties counterpart (see C# Parameterless Properties (Part I) and C# Parameterless Properties (Part II)).
Now, let’s turn our focus to what is happening under the hood. As we did before, we load our simple program in IL DASM and analyse the various elements. Our source program is:

  1. class Program
  2. {
  3.     public int Age { get; private set; }
  4.  
  5.     static void Main(string[] args)
  6.     {
  7.     }
  8. }

Loading the PE code in IL DASM yields the following:

C# Automatically Implemented Properties - IL DASM

C# Automatically Implemented Properties - IL DASM


On loading our assembly in IL DASM, it immediately strikes our attention the private variable <Age>k_BackingField. This is a variable that is generated by the compiler to store the local value of Age. In fact, this is the main difference between the IL code we saw in our previous articles and this one. One can compare the code below with that in C# Parameterless Properties (Part 1). For our convenience here is the code for the getter method from the previous article and the IL code produced by Automatic Properties.

‘Traditional’ Properties

  1. .method public hidebysig specialname instance int32 
  2.         get_Age() cil managed
  3. {
  4.   // Code size       12 (0xc)
  5.   .maxstack  1
  6.   .locals init ([0] int32 CS$1$0000)
  7.   nop
  8.   ldarg.0
  9.   ldfld      int32 PropertiesExample.PropertiesClass::age
  10.   stloc.0
  11.   br.s       IL_000a
  12.   ldloc.0
  13.   ret
  14. } // end of method PropertiesClass::get_Age

Automatic Properties

  1. .method public hidebysig specialname instance int32 
  2.         get_Age() cil managed
  3. {
  4.   .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  5.   // Code size       11 (0xb)
  6.   .maxstack  1
  7.   .locals init (int32 V_0)
  8.   IL_0000:  ldarg.0
  9.   IL_0001:  ldfld      int32 AutomaticallyImplementedProperties.Program::'k__BackingField'
  10.   IL_0006:  stloc.0
  11.   IL_0007:  br.s       IL_0009
  12.   IL_0009:  ldloc.0
  13.   IL_000a:  ret
  14. } // end of method Program::get_Age

We conclude our brief investigation here, and I wish that it clarifies how Automatic Properties work.