This is the seventh 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
Table of Contents
45. Should we always avoid boxing?
Boxing is expensive, it wraps value types into reference instances so we lose some performance. However, take this code:
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 |
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; public class Program { public static void Main() { var source = Enumerable.Range(0, 1000).ToArray(); while(true) { Foo result = UnsafeParallel(source, n => { Thread.Sleep(0); return new Foo { A = n, B = n, C = n, D = n, E = n }; }); if (result.A != result.B || result.A != result.C || result.A != result.D || result.A != result.E) { Console.WriteLine("Tearing detected!"); Console.WriteLine(result.A); Console.WriteLine(result.B); Console.WriteLine(result.C); Console.WriteLine(result.D); Console.WriteLine(result.E); break; } } } static T UnsafeParallel<T>(IEnumerable<int> source, Func<int, T> action) { T result = default(T); Parallel.ForEach(source, (i, state) => { result = action(i); state.Stop(); }); return result; } static T SafeParallel<T>(IEnumerable<int> source, Func<int, T> action) { object result = default(T); Parallel.ForEach(source, (i, state) => { result = action(i); state.Stop(); }); return (T)result; } } struct Foo { public int A { get; set; } public int B { get; set; } public int C { get; set; } public int D { get; set; } public int E { get; set; } } |
The only difference between UnsafeParallel
and SafeParallel
is that the former stores the result in a T
variable, the latter stores just an object.
Now, what happens if you use value types? Since they can be bigger than a reference, they do not need to be stored atomically. So if two threads try to write to result
at the same time we may get the write tearing. Run the code to see that it is happening indeed — we get mixed content.
However, if we use SafeParallel
, the value type is boxed so it is stored atomically (because only reference needs to be stored). Thanks to that we never get broken data.
46. What happens if first delegate method throws an exception?
It stops executing other callbacks, as shown here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; public class Program { delegate void Foo(); public static void Main() { Foo foo = () => Console.WriteLine("First"); foo += () => throw new Exception("Second"); foo += () => Console.WriteLine("Third"); try{ foo(); }catch(Exception e){ Console.WriteLine(e); } } } |
Output:
1 2 3 4 |
First System.Exception: Second at Program.<>c.<Main>b__1_1() in /home/runner/.code.tio:line 9 at Program.Main() in /home/runner/.code.tio:line 13 |
47. Does foreach
require interface? What is a WellKnownMember
?
No, as shown here:
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 |
using System; public class Program { public static void Main() { var bar = new Bar(); foreach (int item in bar) { Console.WriteLine(item); } } } class Foo { public int Current { get; private set; } private int step; public bool MoveNext() { if (step >= 5) return false; Current = step++; return true; } } class Bar { public Foo GetEnumerator() { return new Foo(); } } |
foreach
requires something with method GetEnumerator
returning something with Current
and bool MoveNext()
. This is a duck typing. The same rule goes for await
as shown here:
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; using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace AwaitOnInteger { class Program { static void Main(string[] args) { WaitForInt().Wait(); } static async Task WaitForInt() { Console.WriteLine($"Waiting starting at {DateTime.Now}"); await 2000; Console.WriteLine($"Waiting finished at {DateTime.Now}"); } } public static class AwaitableInt { public static TaskAwaiter GetAwaiter(this int miliseconds) { return Task.Delay(TimeSpan.FromMilliseconds(miliseconds)).GetAwaiter(); } } } |
Actually, there are a lot of WellKnownMember which are used by the compiler.
48. How does LINQ query syntax work? How is it compiled?
They are compiled to normal Where
, Select
, Join
and other methods. Try decompiling the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using System; using System.Linq; namespace Program { public class Program { public static void Main(string[] args) { var source = Enumerable.Range(0, 100); var filtered = from i in source where i % 3 == 0 select i / 2; } } } |
You should get something similar to:
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 |
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace Program { public class Program { [CompilerGenerated] [Serializable] private sealed class <>c { public static readonly Program.<>c <>9 = new Program.<>c(); public static Func<int, bool> <>9__0_0; public static Func<int, int> <>9__0_1; internal bool <Main>b__0_0(int i) { return i % 3 == 0; } internal int <Main>b__0_1(int i) { return i / 2; } } public static void Main(string[] args) { IEnumerable<int> enumerable = Enumerable.Range(0, 100); IEnumerable<int> arg_2A_0 = enumerable; Func<int, bool> arg_2A_1; if ((arg_2A_1 = Program.<>c.<>9__0_0) == null) { arg_2A_1 = (Program.<>c.<>9__0_0 = new Func<int, bool>(Program.<>c.<>9.<Main>b__0_0)); } IEnumerable<int> arg_4E_0 = arg_2A_0.Where(arg_2A_1); Func<int, int> arg_4E_1; if ((arg_4E_1 = Program.<>c.<>9__0_1) == null) { arg_4E_1 = (Program.<>c.<>9__0_1 = new Func<int, int>(Program.<>c.<>9.<Main>b__0_1)); } IEnumerable<int> enumerable2 = arg_4E_0.Select(arg_4E_1); } } } |
49. What is the difference between Select
in IEnumerable
and IQueryable
?
This is Enumerable.Select
:
1 |
public static System.Collections.Generic.IEnumerable<TResult> Select<TSource,TResult> (this System.Collections.Generic.IEnumerable<TSource> source, Func<TSource,int,TResult> selector); |
This is Queryable.Select
:
1 |
public static System.Linq.IQueryable<TResult> Select<TSource,TResult> (this System.Linq.IQueryable<TSource> source, System.Linq.Expressions.Expression<Func<TSource,int,TResult>> selector); |
Apart from different interfaces, they accept lambda in different way. Enumerable
gets normal lambda and executes it directly. Queryable
takes expression which is just a lambda syntax tree. It is not executed at all, it is later translated to SQL query.
50. How to block access to private members via reflection?
You can use DisablePrivateReflectionAttribute on the assembly level.
You can also use ReflectionPermission.
51. Can you lock a value type?
No and yes (but still no). If you try this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using System; namespace Program { public class Program { public static void Main(string[] args) { int i = 5; lock(i){ } } } } |
you will get:
1 |
Compilation error (line 10, col 9): 'int' is not a reference type as required by the lock statement |
So we cannot just lock it. But we know that lock
uses monitors, so let’s try this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System; using System.Threading; namespace Program { public class Program { public static void Main(string[] args) { int i = 5; bool wasTaken = false; try{ Monitor.Enter(i, ref wasTaken); }finally{ if(wasTaken){ Monitor.Exit(i); } } } } } |
It compiles but crashes, why? This is because we use boxing so we are not locking on the value but on the boxed object. We could store the reference as in here:
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 |
using System; using System.Threading; namespace Program { public class Program { public static void Main(string[] args) { int i = 5; object reference = i; bool wasTaken = false; try { Monitor.Enter(reference, ref wasTaken); } finally { if (wasTaken) { Monitor.Exit(reference); } } } } } |
This works but now we risk locking on different boxed instance. So generally — don’t do that.