How do Properties actually work in C#, for example DateTime.Today?

192 views Asked by At

I just saw some snippet code using the struct DateTime.Today and I can't understand the inner workings of it. Here is the specific line:

if (DateTime.Today.DayOfWeek == DayOfWeek.Monday)

After using Go To Definition F12, I saw that the Today static method returns a DateTime object with the date information in your current computer. I suppose that Today method works as a constructor wrapper. What I can't deduce (more like guessing) is how it's possible to access the property DayOfWeek without instantiating first the Today struct.

Can someone explain me how is this possible? My only guess is that when VS compiles the code to IL maybe it converts this syntactic sugar to:

if ( (DateTime.Today()).DayOfWeek == DayOfWeek.Monday )

Maybe this is as clean as water but I'm a C# newbie so I just can't figure it out.

Thanks in advance!

2

There are 2 answers

7
Bobby Tables On

You guest it right, welcome to the wonderful world of properties.

DateTime.Today is a property which in short is a function generated by the compiler, it translates to DateTime.get_today(). So that expression would actually be

if ( (DateTime.get_today()).DayOfWeek == DayOfWeek.Monday )

Example:

public class Test
{
    private string _lastName = "LName";
    private string _firstName = "FName";
    public string Name { get{
        return _lastName + " " + _firstName;
    } }

    public string GetName()
    {
        return _lastName + " " + _firstName;
    }
}
class Program
{
    static void Main(string[] args)
    {
        var test = new Test();
        Console.WriteLine(test.Name);
        Console.WriteLine(DateTime.Today.DayOfWeek);
    }
}

Decompiled GetName

.method public hidebysig instance string 
        GetName() cil managed
{
  // Code size       28 (0x1c)
  .maxstack  3
  .locals init ([0] string CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      string ConsoleApplication1.Test::_lastName
  IL_0007:  ldstr      " "
  IL_000c:  ldarg.0
  IL_000d:  ldfld      string ConsoleApplication1.Test::_firstName
  IL_0012:  call       string [mscorlib]System.String::Concat(string,
                                                              string,
                                                              string)
  IL_0017:  stloc.0
  IL_0018:  br.s       IL_001a
  IL_001a:  ldloc.0
  IL_001b:  ret
} // end of method Test::GetName

Decompiled Name

.method public hidebysig specialname instance string 
        get_Name() cil managed
{
  // Code size       28 (0x1c)
  .maxstack  3
  .locals init ([0] string CS$1$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      string ConsoleApplication1.Test::_lastName
  IL_0007:  ldstr      " "
  IL_000c:  ldarg.0
  IL_000d:  ldfld      string ConsoleApplication1.Test::_firstName
  IL_0012:  call       string [mscorlib]System.String::Concat(string,
                                                              string,
                                                              string)
  IL_0017:  stloc.0
  IL_0018:  br.s       IL_001a
  IL_001a:  ldloc.0
  IL_001b:  ret
} // end of method Test::get_Name

Decompile Main(method calls)

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       56 (0x38)
  .maxstack  1
  .locals init ([0] class ConsoleApplication1.Test test,
           [1] valuetype [mscorlib]System.DateTime CS$0$0000)
  IL_0000:  nop
  IL_0001:  newobj     instance void ConsoleApplication1.Test::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  callvirt   instance string ConsoleApplication1.Test::get_Name() //here
  IL_000d:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0012:  nop
  IL_0013:  ldloc.0
  IL_0014:  callvirt   instance string ConsoleApplication1.Test::GetName()//here
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_001e:  nop
  IL_001f:  call       valuetype [mscorlib]System.DateTime [mscorlib]System.DateTime::get_Today()//here
  IL_0024:  stloc.1
  IL_0025:  ldloca.s   CS$0$0000
  IL_0027:  call       instance valuetype [mscorlib]System.DayOfWeek [mscorlib]System.DateTime::get_DayOfWeek()
  IL_002c:  box        [mscorlib]System.DayOfWeek
  IL_0031:  call       void [mscorlib]System.Console::WriteLine(object)
  IL_0036:  nop
  IL_0037:  ret
} // end of method Program::Main

As you can see there is absolutely no difference between Name and GetName except the fact that Name is generated by the compiler for you as get_Name.

Update 1

As for DateTime.Today it actually get transformed into

System.DateTime [mscorlib]System.DateTime::get_Today()

What you have to understand is that even if the compiler generates those functions for you, they can't be accessed directly because it generates IL code(assembly code for .NET) not C# (things might have changed with Roslyn the new C# compiler but don't know much about that)

What i recommend, if you're really curious about what really happens in you application, is to use ildasm.exe it allows you to see the IL generated by the compiler. A nice book on the subject it called CLR via C#, i had contact with the 3rd edition but apparently there's a 4th edition now.

3
Alexander Bell On

Pertinent to your question, DateTime.Today is a Property type of DateTime, and as such it has DateTime.DayOfWeek Property as any regular DateTime structure.

In a broader sense regarding how do Properties work in C#: in a first approximation, just for better understanding you may think of Properties as Methods masqueraded to look like a field, so your interpretation of Today as some hypothetical GetToday() method is logically close, but not "academically precise" (see the comments by @CalebB).

I would recommend to familiarize yourself with .NET Properties (https://msdn.microsoft.com/en-us/library/x9fsa0sw.aspx) and DateTime Structure (https://msdn.microsoft.com/en-us/libraRy/system.datetime.aspx)

Hope this may help.