This is the fifth part of the .NET Internals Cookbook series. For your convenience you can find other parts in the table of contents in Part 0 – Table of contents

27. Can two methods differ in return type only?

Not in C#, but they can in IL. See this:

// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly '9ebd3665-50ff-4ecf-b716-a61e4b5c1f81'
{
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module '9ebd3665-50ff-4ecf-b716-a61e4b5c1f81.dll'
// MVID: {79C4F8CD-B0E4-4474-8A80-82F28C5D6F41}
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x01530000


// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi beforefieldinit Program
       extends [mscorlib]System.Object
{
  .method public hidebysig static void  Main() cil managed
  {
    // 
	.entrypoint
    .maxstack  1
    .locals init ([0] int32 x,
             [1] float64 y)
    .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
    .line 6,6 : 2,3 ''
    IL_0000:  nop
    .line 7,7 : 3,17 ''
    IL_0001:  call       int32 Program::Foo()
    IL_0006:  stloc.0
    .line 8,8 : 3,20 ''
    IL_0007:  call       float64 Program::Foo()
    IL_000c:  stloc.1
    .line 9,9 : 2,3 ''
    IL_000d:  ret
  } // end of method Program::Main

  .method private hidebysig static int32 
          Foo() cil managed
  {
    // 
    .maxstack  1
    .locals init ([0] int32 V_0)
    .line 11,11 : 18,19 ''
    IL_0000:  nop
    .line 12,12 : 3,12 ''
    IL_0001:  ldc.i4.5
    IL_0002:  stloc.0
    IL_0003:  br.s       IL_0005

    .line 13,13 : 2,3 ''
    IL_0005:  ldloc.0
    IL_0006:  ret
  } // end of method Program::Foo

  .method private hidebysig static float64 
          Foo() cil managed
  {
    // 
    .maxstack  1
    .locals init ([0] float64 V_0)
    .line 15,15 : 21,22 ''
    IL_0000:  nop
    .line 16,16 : 3,15 ''
    IL_0001:  ldc.r8     10.
    IL_000a:  stloc.0
    IL_000b:  br.s       IL_000d

    .line 17,17 : 2,3 ''
    IL_000d:  ldloc.0
    IL_000e:  ret
  } // end of method Program::Bar

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  ret
  } // end of method Program::.ctor

} // end of class Program


// =============================================================

// 

Interesting part is this:

    IL_0001:  call       int32 Program::Foo()
    IL_0006:  stloc.0
    .line 8,8 : 3,20 ''
    IL_0007:  call       float64 Program::Foo()

The way you call the method determines what methods you can declare since we need to be able to distinguish them. Since in IL you specify the return type when calling the method, you can have two methods with different return types.

28. What is the difference between const and readonly?

See this code:

using System;

public class Program
{
	const int a = 5;
	static readonly int b = 6;
	public static void Main()
	{
		Foo(a);
		Foo(b);
	}
	
	static void Foo(int x){
		Console.WriteLine(x);
	}
}

When you decompile it you get:

IL_0001:  ldc.i4.5
IL_0002:  call       void Program::Foo(int32)
IL_0007:  nop
.line 10,10 : 3,10 ''
IL_0008:  ldsfld     int32 Program::b
IL_000d:  call       void Program::Foo(int32)

Const variable was replaced with raw integer, readonly variable was just used. This may have very unexpected consequences — if you change the value of the const variable and recompile the code, but not the user of this code, then the caller will still use old value.

29. How are arguments passed in C#? By value? By reference? Differently?

Passing by value is easy:

using System;

public class Program
{
	public static void Main()
	{
		int a = 5;
		Foo(a);
		Console.WriteLine(a);
	}
	
	static void Foo(int x){
		x = 6;
	}
}

Output:

5

Here Foo(5) passed the value. The same goes for all value types, they are passed by value so they are copied when passed to a function. Because of that the variable a was not modified.

The same goes for references as well! You can often read that reference types are passed by, well, reference. But it is not true, see this code:

using System;

public class Program
{
	public static void Main()
	{
		object a = new object();
		Foo(a);
		Console.WriteLine(a);
	}
	
	static void Foo(object x){
		x = null;
	}
}

Output:

System.Object

Even though reference was modified in the Foo method, the variable a is not modified. This is because what we pass is a reference, not an object. So reference types are passed in the same way as value types, only not the object is passed (or the value) but the reference to the object.

So to be super precise: references are passed by value but objects are passed by sharing.

C# can pass variables by reference as well:

using System;

public class Program
{
	public static void Main()
	{
		int a = 5;
		Foo(ref a);
		Console.WriteLine(a);
	}
	
	static void Foo(ref int x){
		x = 6;
	}
}

Output:

6

When you execute it you can see that the variable a was modified.

30. In what order are method parameters calculated?

Most of the times this doesn’t matter but for named parameters this can be a little tricky:

using System;

public class Program
{
	public static void Main()
	{
		Foo(b: Identity(5), a: Identity(4));
	}
	
	static void Foo(int a, int b){
		Console.WriteLine($"a = {a}\nb = {b}");
	}
	
	static int Identity(int x){
		Console.WriteLine(x);
		return x;
	}
}

Output:

5
4
a = 4
b = 5

Parameters are calculated in the order specified by the caller, not as specified in the function signature.

31. What is the difference between typeof and GetType?

The former is evaluated during compilation, the latter in runtime. This means that the former doesn’t need an object, as shown here:

using System;

public class Program
{
	public static void Main()
	{
		Type t = typeof(Foo);
		Type u = new Foo().GetType();
	}
}

class Foo{
	public Foo(){
		Console.WriteLine("Constructor!");
	}
}

Decompile and get this:

IL_0001:  ldtoken    Foo
IL_0006:  call       class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000b:  stloc.0
.line 8,8 : 3,32 ''
IL_000c:  newobj     instance void Foo::.ctor()
IL_0011:  call       instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
32. What is behind using and lock? Was it always compiled in this way?

A typical try+finally pattern, like here:

using System;

public class Program
{
	public static void Main()
	{
		lock(new object()){
		}
		
		using(new Foo()){
		}
	}
}
			
class Foo : IDisposable {
	public void Dispose(){}
}

For using we get:

IL_0021:  newobj     instance void Foo::.ctor()
IL_0026:  stloc.2
.line 10,10 : 19,20 ''
.try
{
  IL_0027:  nop
  .line 11,11 : 3,4 ''
  IL_0028:  nop
  IL_0029:  leave.s    IL_0036

  .line 16707566,16707566 : 0,0 ''
}  // end .try
finally
{
  IL_002b:  ldloc.2
  IL_002c:  brfalse.s  IL_0035

  IL_002e:  ldloc.2
  IL_002f:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
  IL_0034:  nop
  IL_0035:  endfinally
  .line 12,12 : 2,3 ''
}  // end handler

So in finally we check if the object is not null and then we call Dispose. It is important to notice that the object is created before the try block!

For locking w get this:

IL_0001:  newobj     instance void [mscorlib]System.Object::.ctor()
IL_0006:  stloc.0
IL_0007:  ldc.i4.0
IL_0008:  stloc.1
.try
{
  IL_0009:  ldloc.0
  IL_000a:  ldloca.s   V_1
  IL_000c:  call       void [mscorlib]System.Threading.Monitor::Enter(object, bool&)
  IL_0011:  nop
  .line 7,7 : 21,22 ''
  IL_0012:  nop
  .line 8,8 : 3,4 ''
  IL_0013:  nop
  IL_0014:  leave.s    IL_0021

  .line 16707566,16707566 : 0,0 ''
}  // end .try
finally
{
  IL_0016:  ldloc.1
  IL_0017:  brfalse.s  IL_0020

  IL_0019:  ldloc.0
  IL_001a:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
  IL_001f:  nop
  IL_0020:  endfinally
  .line 10,10 : 3,19 ''
}  // end handler

Again, object created before the try block.

Before C# 4 lock was using Monitor.Enter(object) method and was called before the try block which could result in a nasty, never released lock. Read more here.

33. What is the .NET calling convention?

For x86 it is clrcall convention which is like Fastcall but passes arguments from left to right.

Take this code:

using System;

public class Program
{
    public static void Main()
    {
        Static(1, 2, 3, 4, 5, 6);
        new Program().Instance(1, 2, 3, 4, 5, 6);
    }

    public static void Static(int a, int b, int c, int d, int e, int f)
    {
    }

    public void Instance(int a, int b, int c, int d, int e, int f)
    {
    }
}

It gives these calls:

02b20465 6a03            push    3
02b20467 6a04            push    4
02b20469 6a05            push    5
02b2046b 6a06            push    6
02b2046d b901000000      mov     ecx,1
02b20472 ba02000000      mov     edx,2
02b20477 ff15484def00    call    dword ptr ds:[0EF4D48h] (Program.Static(Int32, Int32, Int32, Int32, Int32, Int32), mdToken: 06000002)

and

02b20494 6a02            push    2
02b20496 6a03            push    3
02b20498 6a04            push    4
02b2049a 6a05            push    5
02b2049c 6a06            push    6
02b2049e 8b4dfc          mov     ecx,dword ptr [ebp-4]
02b204a1 ba01000000      mov     edx,1
02b204a6 ff15544def00    call    dword ptr ds:[0EF4D54h] (Program.Instance(Int32, Int32, Int32, Int32, Int32, Int32), mdToken: 06000003)
02b204ac 90              nop

For x64 it is Microsoft x64.

Code above gives:

00007ffa`820704a3 c744242005000000 mov     dword ptr [rsp+20h],5
00007ffa`820704ab c744242806000000 mov     dword ptr [rsp+28h],6
00007ffa`820704b3 b901000000      mov     ecx,1
00007ffa`820704b8 ba02000000      mov     edx,2
00007ffa`820704bd 41b803000000    mov     r8d,3
00007ffa`820704c3 41b904000000    mov     r9d,4
00007ffa`820704c9 e8b2fbffff      call    00007ffa`82070080 (Program.Static(Int32, Int32, Int32, Int32, Int32, Int32), mdToken: 0000000006000002)

and

00007ffa`820704eb c744242004000000 mov     dword ptr [rsp+20h],4
00007ffa`820704f3 c744242805000000 mov     dword ptr [rsp+28h],5
00007ffa`820704fb c744243006000000 mov     dword ptr [rsp+30h],6
00007ffa`82070503 488b4df8        mov     rcx,qword ptr [rbp-8]
00007ffa`82070507 ba01000000      mov     edx,1
00007ffa`8207050c 41b802000000    mov     r8d,2
00007ffa`82070512 41b903000000    mov     r9d,3
00007ffa`82070518 e86bfbffff      call    00007ffa`82070088 (Program.Instance(Int32, Int32, Int32, Int32, Int32, Int32), mdToken: 0000000006000003)
34. Can you have const decimal? const string? const Foo?

Yes, see here:

using System;

public class Program
{
	const decimal a = 5;
	const string b = "abc";
	const Foo c = null;
	public static void Main()
	{
	}
}

class Foo
{
}

However, there is a trick here. If you decompile the code, you get this:


//  Microsoft (R) .NET Framework IL Disassembler.  Version 4.0.30319.33440
//  Copyright (c) Microsoft Corporation.  All rights reserved.


// 



// Metadata version: v4.0.30319
.assembly extern mscorlib
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
  .ver 4:0:0:0
}
.assembly 'bb0c0028-1e9e-407c-9c2f-43a28949efba'
{
  .hash algorithm 0x00008004
  .ver 0:0:0:0
}
.module 'bb0c0028-1e9e-407c-9c2f-43a28949efba.dll'
// MVID: {0AA5E085-683A-440A-A7C9-AFE31B57D6E0}
.imagebase 0x10000000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003       // WINDOWS_CUI
.corflags 0x00000001    //  ILONLY
// Image base: 0x014C0000


// =============== CLASS MEMBERS DECLARATION ===================

.class public auto ansi beforefieldinit Program
       extends [mscorlib]System.Object
{
  .field private static initonly valuetype [mscorlib]System.Decimal a
  .field private static literal string b = "abc"
  .field private static literal class Foo c = nullref
  .method public hidebysig static void  Main() cil managed
  {
    // 
    .maxstack  8
    .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
    .line 9,9 : 2,3 ''
    IL_0000:  nop
    .line 10,10 : 2,3 ''
    IL_0001:  ret
  } // end of method Program::Main

  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  ret
  } // end of method Program::.ctor

  .method private hidebysig specialname rtspecialname static 
          void  .cctor() cil managed
  {
    // 
    .maxstack  8
    .line 5,5 : 2,22 ''
    IL_0000:  ldc.i4.5
    IL_0001:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
    IL_0006:  stsfld     valuetype [mscorlib]System.Decimal Program::a
    IL_000b:  ret
  } // end of method Program::.cctor

} // end of class Program

.class private auto ansi beforefieldinit Foo
       extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname 
          instance void  .ctor() cil managed
  {
    // 
    .maxstack  8
    IL_0000:  ldarg.0
    IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
    IL_0006:  nop
    IL_0007:  ret
  } // end of method Foo::.ctor

} // end of class Foo


// =============================================================

// 

Interesting parts are:

.field private static initonly valuetype [mscorlib]System.Decimal a
.method private hidebysig specialname rtspecialname static 
          void  .cctor() cil managed
  {
    // 
    .maxstack  8
    .line 5,5 : 2,22 ''
    IL_0000:  ldc.i4.5
    IL_0001:  newobj     instance void [mscorlib]System.Decimal::.ctor(int32)
    IL_0006:  stsfld     valuetype [mscorlib]System.Decimal Program::a
    IL_000b:  ret
  } // end of method Program::.cctor

So you can see that the decimal is not a constant but readonly, it is initialized in the type constructor.

35. Can you pass a volatile variable by reference?

Yes and no. This code compiles:

using System;

public class Program
{
	static volatile int a = 5;
	public static void Main()
	{
		Foo(ref a);
	}
	
	static void Foo(ref int x){
	}
}

But it shows warning that 'Program.a': a reference to a volatile field will not be treated as volatile.