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:
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;
publicclassProgram
{
delegate voidFoo();
publicstaticvoidMain()
{
Foo foo=()=>Console.WriteLine("First");
foo+=()=>thrownewException("Second");
foo+=()=>Console.WriteLine("Third");
try{
foo();
}catch(Exceptione){
Console.WriteLine(e);
}
}
}
Output:
1
2
3
4
First
System.Exception:Second
at Program.<>c.<Main>b__1_1()in/home/runner/.code.tio:line9
at Program.Main()in/home/runner/.code.tio:line13
47. Does foreach require interface? What is a WellKnownMember?
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;
namespaceAwaitOnInteger
{
classProgram
{
staticvoidMain(string[]args)
{
WaitForInt().Wait();
}
staticasync Task WaitForInt()
{
Console.WriteLine($"Waiting starting at {DateTime.Now}");
await2000;
Console.WriteLine($"Waiting finished at {DateTime.Now}");
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?
Compilation error(line10,col9):'int'isnotareference type asrequired 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;
namespaceProgram
{
publicclassProgram
{
publicstaticvoidMain(string[]args)
{
inti=5;
boolwasTaken=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;
namespaceProgram
{
publicclassProgram
{
publicstaticvoidMain(string[]args)
{
inti=5;
objectreference=i;
boolwasTaken=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.