.NET Internals Cookbook Part 8 — C# gotchas

This is the eighth 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

52. Can you have class in a class? Class in an interface? Interface in a class? Interface in an interface?

Yes. No. Yes. No.:

using System; using System.Threading; namespace Program { public class Program { public static void Main(string[] args) { } } } class A { class ClassInAClass{ } } interface B{ class ClassInAnInterface { } } class C{ interface InterfaceInAClass { } } interface D{ interface InterfaceInAnInterface { } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using System ; using System . Threading ; namespace Program { public class Program { public static void Main ( string [ ] args ) { } } } class A { class ClassInAClass { } } interface B { class ClassInAnInterface { } } class C { interface InterfaceInAClass { } } interface D { interface InterfaceInAnInterface { } }

You can put class in a class and interface in a class. You can have neither class nor interface in an interface. That is because an interface is a special kind of type. It is defined in Partition I of the Ecma International/ISO standard as “a named group of methods, locations, and other contracts that shall be implemented by any object type that supports the interface contract of the same name.” So it is not a real type but just a named descriptor of methods (and properties, which are methods) exposed by the type. To check this out you can actually implement an interface without implementing it:

using System; namespace Program { public class Program { public static void Main(string[] args) { Foo foo = new Baz(); foo.Method(); } } interface Foo { void Method(); } class Bar { public void Method() { Console.WriteLine("Bar!"); } } class Baz : Bar, Foo { } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 using System ; namespace Program { public class Program { public static void Main ( string [ ] args ) { Foo foo = new Baz ( ) ; foo . Method ( ) ; } } interface Foo { void Method ( ) ; } class Bar { public void Method ( ) { Console . WriteLine ( "Bar!" ) ; } } class Baz : Bar , Foo { } }

See that Baz gets a method which has the expected signature but it is not a method written explicitly to implement the interface.

What’s more, depending on the assembly the classes are compiled differently. If Bar and Baz are in the same assembly then the Bar::Method is compiled as virtual final ! So this method is virtual even though it was not marked as such. But if those classes are in different assemblies then Bar::Method is not virtual but there is a virtual method in Baz which delegates the call to the method in the base class.

Why is it so? Interface call needs to be polymorphic so we need to have the virtual modifier.

53. How is switch compiled?

It depends on the compiler version, variable type, number of labels. See this in .NET 4.5, VS 2017, C# 7:

static int SwitchAsIfElse(int x) { switch (x) { case 1: return 1; case 2: return 2; default: return -1; } } 1 2 3 4 5 6 7 8 9 10 11 12 static int SwitchAsIfElse ( int x ) { switch ( x ) { case 1 : return 1 ; case 2 : return 2 ; default : return - 1 ; } }

This is compiled to cascade of ifs and elses:

private static int SwitchAsIfElse(int x) { int result; if (x != 1) { if (x != 2) { result = -1; } else { result = 2; } } else { result = 1; } return result; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private static int SwitchAsIfElse ( int x ) { int result ; if ( x != 1 ) { if ( x != 2 ) { result = - 1 ; } else { result = 2 ; } } else { result = 1 ; } return result ; }

This:

static int SwitchAsSwitch(int x) { switch (x) { case 1: return 1; case 2: return 2; case 3: return 3; case 4: return 4; case 5: return 5; case 6: return 6; default: return -1; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static int SwitchAsSwitch ( int x ) { switch ( x ) { case 1 : return 1 ; case 2 : return 2 ; case 3 : return 3 ; case 4 : return 4 ; case 5 : return 5 ; case 6 : return 6 ; default : return - 1 ; } }

is compiled to switch:

private static int SwitchAsSwitch(int x) { int result; switch (x) { case 1: result = 1; break; case 2: result = 2; break; case 3: result = 3; break; case 4: result = 4; break; case 5: result = 5; break; case 6: result = 6; break; default: result = -1; break; } return result; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 private static int SwitchAsSwitch ( int x ) { int result ; switch ( x ) { case 1 : result = 1 ; break ; case 2 : result = 2 ; break ; case 3 : result = 3 ; break ; case 4 : result = 4 ; break ; case 5 : result = 5 ; break ; case 6 : result = 6 ; break ; default : result = - 1 ; break ; } return result ; }

This:

static void SwitchAsIfElse(string x) { switch (x) { case "1": break; case "2": break; case "3": break; case "4": break; case "5": break; case "6": break; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 static void SwitchAsIfElse ( string x ) { switch ( x ) { case "1" : break ; case "2" : break ; case "3" : break ; case "4" : break ; case "5" : break ; case "6" : break ; } }

is compiled to if else again:

private static void SwitchAsIfElse(string x) { if (!(x == "1")) { if (!(x == "2")) { if (!(x == "3")) { if (!(x == "4")) { if (!(x == "5")) { if (!(x == "6")) { } } } } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private static void SwitchAsIfElse ( string x ) { if ( ! ( x == "1" ) ) { if ( ! ( x == "2" ) ) { if ( ! ( x == "3" ) ) { if ( ! ( x == "4" ) ) { if ( ! ( x == "5" ) ) { if ( ! ( x == "6" ) ) { } } } } } } }

However, this:

static void SwitchWithHashes(string x) { switch (x) { case "1": break; case "2": break; case "3": break; case "4": break; case "5": break; case "6": break; case "7": break; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static void SwitchWithHashes ( string x ) { switch ( x ) { case "1" : break ; case "2" : break ; case "3" : break ; case "4" : break ; case "5" : break ; case "6" : break ; case "7" : break ; } }

is compiled to code using hashes:

private static void SwitchWithHashes(string x) { uint num = <PrivateImplementationDetails>.ComputeStringHash(x); if (num <= 839689206u) { if (num != 806133968u) { if (num != 822911587u) { if (num == 839689206u) { if (!(x == "7")) { } } } else { if (!(x == "4")) { } } } else { if (!(x == "5")) { } } } else { if (num <= 873244444u) { if (num != 856466825u) { if (num == 873244444u) { if (!(x == "1")) { } } } else { if (!(x == "6")) { } } } else { if (num != 906799682u) { if (num == 923577301u) { if (!(x == "2")) { } } } else { if (!(x == "3")) { } } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 private static void SwitchWithHashes ( string x ) { uint num = < PrivateImplementationDetails > . ComputeStringHash ( x ) ; if ( num <= 839689206u ) { if ( num != 806133968u ) { if ( num != 822911587u ) { if ( num == 839689206u ) { if ( ! ( x == "7" ) ) { } } } else { if ( ! ( x == "4" ) ) { } } } else { if ( ! ( x == "5" ) ) { } } } else { if ( num <= 873244444u ) { if ( num != 856466825u ) { if ( num == 873244444u ) { if ( ! ( x == "1" ) ) { } } } else { if ( ! ( x == "6" ) ) { } } } else { if ( num != 906799682u ) { if ( num == 923577301u ) { if ( ! ( x == "2" ) ) { } } } else { if ( ! ( x == "3" ) ) { } } } } }

Also, older compilers could use dictionaries, as shown here.

54. What is __makeref ? __refvalue ? __reftype ? __arglist ?

Those are keywords unrecognized by ReSharper and used pretty rarely to do some memory tricks. For first three you can see Custom memory allocation in C#. For __arglist see this code:

using System; using System.Threading; namespace Program { public class Program { public static void Main(string[] args) { ShowArgList(__arglist(5, 6.0, "abc", new object())); } public static void ShowArgList(__arglist) { Console.WriteLine("ShowArgList"); ArgIterator ai = new ArgIterator(__arglist); while (ai.GetRemainingCount() > 0) { TypedReference tr = ai.GetNextArg(); Console.WriteLine(TypedReference.ToObject(tr)); } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 using System ; using System . Threading ; namespace Program { public class Program { public static void Main ( string [ ] args ) { ShowArgList ( __arglist ( 5 , 6.0 , "abc" , new object ( ) ) ) ; } public static void ShowArgList ( __arglist ) { Console . WriteLine ( "ShowArgList" ) ; ArgIterator ai = new ArgIterator ( __arglist ) ; while ( ai . GetRemainingCount ( ) > 0 ) { TypedReference tr = ai . GetNextArg ( ) ; Console . WriteLine ( TypedReference . ToObject ( tr ) ) ; } } } }

Interestingly, call is compiled to:

IL_0015: call vararg void Program.Program::ShowArgList(..., int32, float64, string, object) 1 2 3 4 5 IL_0015 : call vararg void Program . Program :: ShowArgList ( . . . , int32 , float64 , string , object )

55. How do you make union in C#?

You need to specify struct memory layout, as shown here:

using System; using System.Threading; using System.Runtime.InteropServices; namespace Program { public class Program { public static void Main(string[] args) { var array = new ByteArray(); array.Int = 0xBADF00D; Console.WriteLine(array.Int); Console.WriteLine(array.Byte1.ToString("X")); Console.WriteLine(array.Byte2.ToString("X")); Console.WriteLine(array.Byte3.ToString("X")); Console.WriteLine(array.Byte4.ToString("X")); } } } [StructLayout(LayoutKind.Explicit)] struct ByteArray { [FieldOffset(0)] public byte Byte1; [FieldOffset(1)] public byte Byte2; [FieldOffset(2)] public byte Byte3; [FieldOffset(3)] public byte Byte4; [FieldOffset(0)] public int Int; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 using System ; using System . Threading ; using System . Runtime . InteropServices ; namespace Program { public class Program { public static void Main ( string [ ] args ) { var array = new ByteArray ( ) ; array . Int = 0xBADF00D ; Console . WriteLine ( array . Int ) ; Console . WriteLine ( array . Byte1 . ToString ( "X" ) ) ; Console . WriteLine ( array . Byte2 . ToString ( "X" ) ) ; Console . WriteLine ( array . Byte3 . ToString ( "X" ) ) ; Console . WriteLine ( array . Byte4 . ToString ( "X" ) ) ; } } } [ StructLayout ( LayoutKind . Explicit ) ] struct ByteArray { [ FieldOffset ( 0 ) ] public byte Byte1 ; [ FieldOffset ( 1 ) ] public byte Byte2 ; [ FieldOffset ( 2 ) ] public byte Byte3 ; [ FieldOffset ( 3 ) ] public byte Byte4 ; [ FieldOffset ( 0 ) ] public int Int ; }

Output:

195948557 D F0 AD B 1 2 3 4 5 195948557 D F0 AD B

56. Can x == x ever give false? What about x.Equals(x) ?

Yes and no:

using System; using System.Threading; namespace Program { public class Program { public static void Main(string[] args) { Console.WriteLine(Double.NaN == Double.NaN); Console.WriteLine(Double.NaN.Equals(Double.NaN)); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 using System ; using System . Threading ; namespace Program { public class Program { public static void Main ( string [ ] args ) { Console . WriteLine ( Double . NaN == Double . NaN ) ; Console . WriteLine ( Double . NaN . Equals ( Double . NaN ) ) ; } } }

Output:

False True 1 2 False True

Funny thing is that dotNetFiddle shows warning in first line: Comparison made to the same variable; did you mean to compare something else?

NaN is never == to itself, this is specified by IEEE 754 standard. However, Equals contract says that anything must be equal to itself.

57. Can you have a global field in C#?

In C# no. You can do that in IL:

.assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } .assembly 'ad1e7839-aeb9-4422-908f-bfe08bf20efd' { .hash algorithm 0x00008004 .ver 0:0:0:0 } .module 'ad1e7839-aeb9-4422-908f-bfe08bf20efd.dll' // MVID: {3CDA1679-564A-4E02-9A1B-77B7E32EA082} .imagebase 0x10000000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000001 // ILONLY // Image base: 0x01270000 // =============== CLASS MEMBERS DECLARATION =================== .field static int32 X .class public auto ansi beforefieldinit Program.Program extends [mscorlib]System.Object { .method public hidebysig static void Main(string[] args) cil managed { // .entrypoint .maxstack 8 .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}' .line 10,10 : 3,4 '' IL_0000: nop .line 11,11 : 4,25 '' IL_0001: ldsfld int32 X IL_0006: call void [mscorlib]System.Console::WriteLine(int32) IL_000b: nop .line 12,12 : 3,4 '' IL_000c: 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 7,7 : 3,20 '' IL_0000: ldc.i4.5 IL_0001: stsfld int32 Program.Program::X IL_0006: ret } // end of method Program::.cctor } // end of class Program.Program // ============================================================= // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 . assembly extern mscorlib { . publickeytoken = ( B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. . ver 4 : 0 : 0 : 0 } . assembly 'ad1e7839-aeb9-4422-908f-bfe08bf20efd' { . hash algorithm 0x00008004 . ver 0 : 0 : 0 : 0 } . module 'ad1e7839-aeb9-4422-908f-bfe08bf20efd.dll' // MVID: {3CDA1679-564A-4E02-9A1B-77B7E32EA082} . imagebase 0x10000000 . file alignment 0x00000200 . stackreserve 0x00100000 . subsystem 0x0003 // WINDOWS_CUI . corflags 0x00000001 // ILONLY // Image base: 0x01270000 // =============== CLASS MEMBERS DECLARATION =================== . field static int32 X . class public auto ansi beforefieldinit Program . Program extends [ mscorlib ] System . Object { . method public hidebysig static void Main ( string [ ] args ) cil managed { // . entrypoint . maxstack 8 . language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}' , '{994B45C4-E6E9-11D2-903F-00C04FA302A1}' , '{5A869D0B-6611-11D3-BD2A-0000F80849BD}' . line 10 , 10 : 3 , 4 '' IL_0000 : nop . line 11 , 11 : 4 , 25 '' IL_0001 : ldsfld int32 X IL_0006 : call void [ mscorlib ] System . Console :: WriteLine ( int32 ) IL_000b : nop . line 12 , 12 : 3 , 4 '' IL_000c : 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 7 , 7 : 3 , 20 '' IL_0000 : ldc . i4 . 5 IL_0001 : stsfld int32 Program . Program :: X IL_0006 : ret } // end of method Program::.cctor } // end of class Program.Program // ============================================================= //

See that variable X is outside of any class or namespace.

58. Can you have try/catch/finally in method with yield/async?

See this:

using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Program { public class Program { public static void Main(string[] args) { } static IEnumerable<int> YieldWithTryCatchFinally() { yield return 1; try { yield return 2; } catch { yield return 3; } finally { yield return 4; } } static IEnumerable<int> YieldWithTryFinally() { yield return 1; try { yield return 2; } finally { yield return 3; } } static async Task AwaitWithTryCatchFinally() { await Task.Yield(); try { await Task.Yield(); } catch { await Task.Yield(); } finally { await Task.Yield(); } } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 using System ; using System . Collections . Generic ; using System . Threading . Tasks ; namespace Program { public class Program { public static void Main ( string [ ] args ) { } static IEnumerable < int > YieldWithTryCatchFinally ( ) { yield return 1 ; try { yield return 2 ; } catch { yield return 3 ; } finally { yield return 4 ; } } static IEnumerable < int > YieldWithTryFinally ( ) { yield return 1 ; try { yield return 2 ; } finally { yield return 3 ; } } static async Task AwaitWithTryCatchFinally ( ) { await Task . Yield ( ) ; try { await Task . Yield ( ) ; } catch { await Task . Yield ( ) ; } finally { await Task . Yield ( ) ; } } } }

And given errors:

Compilation error (line 18, col 5): Cannot yield a value in the body of a try block with a catch clause Compilation error (line 22, col 5): Cannot yield a value in the body of a catch clause Compilation error (line 26, col 5): Cannot yield in the body of a finally clause Compilation error (line 39, col 5): Cannot yield in the body of a finally clause 1 2 3 4 Compilation error ( line 18 , col 5 ) : Cannot yield a value in the body of a try block with a catch clause Compilation error ( line 22 , col 5 ) : Cannot yield a value in the body of a catch clause Compilation error ( line 26 , col 5 ) : Cannot yield in the body of a finally clause Compilation error ( line 39 , col 5 ) : Cannot yield in the body of a finally clause

You can have yield return in try if and only if you don’t have catch block. You cannot yield in finally.

You can have await anywhere. It was not always the case, in older C# version you couldn’t await in finally.