.NET Internals Cookbook Part 11 — Various C# riddles

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

75. What is the TimeSpan resolution?

It’s 100 nanoseconds, as specified by the documentation:

The value of a TimeSpan object is the number of ticks that equal the represented time interval. A tick is equal to 100 nanoseconds, or one ten-millionth of a second. 1 The value of a TimeSpan object is the number of ticks that equal the represented time interval . A tick is equal to 100 nanoseconds , or one ten - millionth of a second .

76. What is shared between app domains?

Quite a few things:

Domain neutral Type objects

objects Reflection types like PropertyInfo

Strings

Threads

If you share one physical object between multiple app domains, it is called marshal-by-bleed . This is important because if you try locking such an object, you may effectively get a lock across different app domains. So if you do lock(typeof(Foo)) you may lock multiple app domains, which can be very nasty in ASP.NET applications (if you have multiple apps running on the same pool as they are separated by app domains).

On the other hand, if you want to share something in this way, see this code:

[LoaderOptimization(LoaderOptimization.MultiDomain)] static public void Main(string[] args) { // To load our assembly appdomain neutral we need to use MultiDomain on our hosting and child domain // If not we would get different Method tables for the same types which would result in InvalidCastExceptions // for the same type. var other = AppDomain.CreateDomain("Test"+i.ToString(), AppDomain.CurrentDomain.Evidence, new AppDomainSetup { LoaderOptimization = LoaderOptimization.MultiDomain, }); // Create gate object in other appdomain DomainGate gate = (DomainGate)other.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(DomainGate).FullName); // now lets create some data CrossDomainData data = new CrossDomainData(); data.Input = Enumerable.Range(0, 10).ToList(); // process it in other AppDomain DomainGate.Send(gate, data); // Display result calculated in other AppDomain Console.WriteLine("Calculation in other AppDomain got: {0}", data.Aggregate); } } 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 [ LoaderOptimization ( LoaderOptimization . MultiDomain ) ] static public void Main ( string [ ] args ) { // To load our assembly appdomain neutral we need to use MultiDomain on our hosting and child domain // If not we would get different Method tables for the same types which would result in InvalidCastExceptions // for the same type. var other = AppDomain . CreateDomain ( "Test" + i . ToString ( ) , AppDomain . CurrentDomain . Evidence , new AppDomainSetup { LoaderOptimization = LoaderOptimization . MultiDomain , } ) ; // Create gate object in other appdomain DomainGate gate = ( DomainGate ) other . CreateInstanceAndUnwrap ( Assembly . GetExecutingAssembly ( ) . FullName , typeof ( DomainGate ) . FullName ) ; // now lets create some data CrossDomainData data = new CrossDomainData ( ) ; data . Input = Enumerable . Range ( 0 , 10 ) . ToList ( ) ; // process it in other AppDomain DomainGate . Send ( gate , data ) ; // Display result calculated in other AppDomain Console . WriteLine ( "Calculation in other AppDomain got: {0}" , data . Aggregate ) ; } }

As a side note: .NET Core doesn’t expose app domains anymore. It uses them internally (at least one of them) but there is no API for it anymore.

77. Is Timer removed when there is no reference to it but it has callbacks?

Yes, it is. See this:

using System; using System.Threading; namespace Program { public class Program { public static void Main(string[] args) { var stateTimer = new Timer(t => Console.WriteLine("Timer!"), null, 50, 50); Thread.Sleep(70); stateTimer = null; GC.Collect(); GC.WaitForPendingFinalizers(); Thread.Sleep(1500); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System ; using System . Threading ; namespace Program { public class Program { public static void Main ( string [ ] args ) { var stateTimer = new Timer ( t = > Console . WriteLine ( "Timer!" ) , null , 50 , 50 ) ; Thread . Sleep ( 70 ) ; stateTimer = null ; GC . Collect ( ) ; GC . WaitForPendingFinalizers ( ) ; Thread . Sleep ( 1500 ) ; } } }

Output (with callback executed just once):

Timer! 1 Timer !

Documentation also mentions that:

As long as you are using a Timer, you must keep a reference to it. As with any managed object, a Timer is subject to garbage collection when there are no references to it. The fact that a Timer is still active does not prevent it from being collected. 1 As long as you are using a Timer , you must keep a reference to it . As with any managed object , a Timer is subject to garbage collection when there are no references to it . The fact that a Timer is still active does not prevent it from being collected .

78. What is the difference between delegate { ... } and delegate() { ... } ?

The former accepts any parameters, the latter has no parameters at all. See this:

using System; namespace Program { public delegate void Foo(); public delegate void Bar(int x); public class Program { public static void Main(string[] args) { Foo a = delegate {}; Bar b = delegate {}; Foo c = delegate() {}; Bar d = delegate() {}; } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 using System ; namespace Program { public delegate void Foo ( ) ; public delegate void Bar ( int x ) ; public class Program { public static void Main ( string [ ] args ) { Foo a = delegate { } ; Bar b = delegate { } ; Foo c = delegate ( ) { } ; Bar d = delegate ( ) { } ; } } }

The last line doesn’t compile as you need to accept one parameter.

79. Can you have DllMain in C#?

You can’t in C#, but you can in IL. This is called Module Initializer. You just need to define a type initializer (static constructor) outside of any type (as we already know that you can have global methods and fields in IL):

.assembly extern mscorlib { .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. .ver 4:0:0:0 } .assembly 'f66978ab-21bd-416d-b1fd-2cc8d4467cef' { .hash algorithm 0x00008004 .ver 0:0:0:0 } .module 'f66978ab-21bd-416d-b1fd-2cc8d4467cef.dll' // MVID: {15C95762-8B92-4BE6-9ABD-A280C050496E} .imagebase 0x10000000 .file alignment 0x00000200 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000001 // ILONLY // Image base: 0x015B0000 .method public hidebysig specialname rtspecialname void .cctor() cil managed { .maxstack 8 IL_0000: nop IL_0001: ldstr "Module initializer!" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // =============== CLASS MEMBERS DECLARATION =================== .class public auto ansi beforefieldinit Program.Program extends [mscorlib]System.Object { .method public hidebysig static void Main(string[] args) cil managed { // .entrypoint .maxstack 8 IL_0000: nop 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 } // 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 . assembly extern mscorlib { . publickeytoken = ( B7 7A 5C 56 19 34 E0 89 ) // .z\V.4.. . ver 4 : 0 : 0 : 0 } . assembly 'f66978ab-21bd-416d-b1fd-2cc8d4467cef' { . hash algorithm 0x00008004 . ver 0 : 0 : 0 : 0 } . module 'f66978ab-21bd-416d-b1fd-2cc8d4467cef.dll' // MVID: {15C95762-8B92-4BE6-9ABD-A280C050496E} . imagebase 0x10000000 . file alignment 0x00000200 . stackreserve 0x00100000 . subsystem 0x0003 // WINDOWS_CUI . corflags 0x00000001 // ILONLY // Image base: 0x015B0000 . method public hidebysig specialname rtspecialname void . cctor ( ) cil managed { . maxstack 8 IL_0000 : nop IL_0001 : ldstr "Module initializer!" IL_0006 : call void [ mscorlib ] System . Console :: WriteLine ( string ) IL_000b : nop IL_000c : ret } // =============== CLASS MEMBERS DECLARATION =================== . class public auto ansi beforefieldinit Program . Program extends [ mscorlib ] System . Object { . method public hidebysig static void Main ( string [ ] args ) cil managed { // . entrypoint . maxstack 8 IL_0000 : nop 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 } // end of class Program.Program // ============================================================= //

This must be a type constructor. If you put an instance constructor you will get the following exception:

Unhandled Exception: System.TypeLoadException: Global Instance Constructor. 1 Unhandled Exception : System . TypeLoadException : Global Instance Constructor .

80. Can you implement your own Nullable< T> ?

No. You can implement structure which would be similar in nature, with the same properties, but Nullable has special runtime support.

First, you cannot have Nullable of Nullable , even though the signature doesn’t prevent it:

[System.Serializable] public struct Nullable<T> where T : struct 1 2 [ System . Serializable ] public struct Nullable < T > where T : struct

Second, you cannot override GetType so you will have different results:

using System; namespace Program { public class Program { public static void Main(string[] args) { Nullable<int> i = 5; Console.WriteLine(i.GetType()); OwnNullable<int> j = 6; Console.WriteLine(j.GetType()); } } } struct OwnNullable<T> where T : struct { private T field; public OwnNullable(T value) { field = value; } public static implicit operator OwnNullable<T>(T value) { return new OwnNullable<T>(value); } } 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 ) { Nullable < int > i = 5 ; Console . WriteLine ( i . GetType ( ) ) ; OwnNullable < int > j = 6 ; Console . WriteLine ( j . GetType ( ) ) ; } } } struct OwnNullable < T > where T : struct { private T field ; public OwnNullable ( T value ) { field = value ; } public static implicit operator OwnNullable < T > ( T value ) { return new OwnNullable < T > ( value ) ; } }

Output:

System.Int32 OwnNullable`1[System.Int32] 1 2 System . Int32 OwnNullable ` 1 [ System . Int32 ]

Third, you cannot assign null to instance of your type.

There are other differences around operators and method calls, so generally you can’t do that on your own.

81. Can you have more visible type inherit from less visible one?

You can do that with interfaces, see this:

using System; namespace Program { public class Program { public static void Main(string[] args) { var foo = new Foo(); } } } public class Foo { private interface IBar { } public class Bar : IBar { } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using System ; namespace Program { public class Program { public static void Main ( string [ ] args ) { var foo = new Foo ( ) ; } } } public class Foo { private interface IBar { } public class Bar : IBar { } }

It compiles and runs correctly.