C#

C# Tech

CLR + JIT

A program is executed. A process is created by the OS, and within its single thread it starts loading code to execute. In a .NET app, a single AppDomain is created by the CLR. The app’s executing assembly (the .EXE) is loaded into this AppDomain and begins execution. The app can spawn new processes, create AppDomains, load other assemblies into these domains, and then create new Threads to execute code in any of these AppDomains.

C# Code > C# Compiler > IL > .NET Runtime > JIT Compiler > Machinecode > Execution

When you compile your C# code it gets turned into IL that the CLR understands. The IL is the same for all languages running on top of the CLR. Metadata from every class and every method is included in the PE header of the resulting executable (dll or exe). If you’re producing an executable the PE Header also includes a conventional bootstrapper which is in charge of loading the CLR (Common language runtime) when you execute you executable. The bootstraper initializes the CLR (mainly by loading the mscorlib assembly) and instructs it to execute your assembly. The CLR executes your main entry. Now, classes have a table which hold the addresses of the method functions, so that when you call MyMethod, this table is searched and then a corresponding call to the address is made. Upon start ALL entries for all tables have the address of the JIT compiler. When a call to one of such method is made, the JIT is invoked instead of the actual method and takes control. The JIT then compiles the IL into actual machine code for the appropiate architecture. Once the code is compiled the JIT goes into the method table and replaces the address with the one of the compiled code, so that every subsequent call no longer invokes the JIT. Finally, the JIT handles the execution to the compiled code.

https://stackoverflow.com/a/5110302 has great diagram on how the JIT works.

Garbage Collection

https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

Statements

Switch


switch (item)
{
   case 0:
      break;
   case int val:
      sum += val;
      break;
   case int[] array when array.Length > 0:
      sum += Sum(array);
      break;
   case null:
      break;
   default:
      throw new Exception("unknown item");
}

Expressions

Switch

Orientation ToOrientation(Direction direction) => direction switch
{
    Direction.Up    => Orientation.North,
    Direction.Right => Orientation.East,
    Direction.Down  => Orientation.South,
    Direction.Left  => Orientation.West,
    _ => throw new ArgumentOutOfRangeException(nameof(direction)),
};

Methods

Functions

public static void UseParams(params int[] x) => x.sum();

Lambdas

Can be static (non-capturing):

data.OrderBy(static e => e);

Parameters

Properties

public double Hours
{
   get { return _seconds / 3600; }
   set {
      if (value < 0 || value > 24)
         throw new ArgumentOutOfRangeException();
      _seconds = value * 3600;
   }
}

// Indexers are written as properties
public T this[int i]
{
    get => arr[i];
    set => arr[i] = value;
}

Delegates, Lambdas

Delegate defines a method signature.

// creates a delegate with the name Parse and signature here
delegate Expression Parse(string s);
// ParseInt satisfies the delegate signature
static Expression ParseInt(string s) => new Expression(int.Parse(s));

static Expression ToExpression(Parse p, string s) => p(s);

Action<>, Func<>, Predicate<> = Func<T, bool> are built-in generic delegates.

Cast

Type constraints

public class AClass<T1,T2> : BaseClass
    where T1: IInterface
    where T2: notnull

Type checks

class B { }
class A : B { }

B b = ... ;
A a = ... ;

b.GetType() == typeof(B);      // true
a.GetType() == typeof(A);      // true
a.GetType() == typeof(B);      // false
typeof(A) == typeof(B);        // false
b is B;                        // true
a is B;                        // true

Covariance and Contravariance

Enable implicit reference conversion for array types, delegate types, and generic type arguments. Covariance preserves assignment compatibility and contravariance reverses it.

// Assignment compatibility.
string str = "test";
// An object of a more derived type is assigned to an object of a less derived type.
object obj = str;

// Covariance.
IEnumerable<string> strings = new List<string>();
// More derived type argument is assigned to a less derived type.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;

// Contravariance.
Action<object> actObject = SetObject;
// Less derived type argument is assigned to a more derived type.
// Assignment compatibility is reversed.
Action<string> actString = actObject;

Viewing subtypes as subsets, covariance allows you to go out of the subset. Contravariance allows you to go in.

public interface ICovariant<out T> {}
public interface IContravariant<in T> {}

Formatting

Number formatting

0,,.00#M is a millions format.

Classes

class Name
{// ...
    public void Deconstruct(out string first, out string last)
    {
        first = First;
        last = Last;
    }
}

var name = new Name("x", "y");
var (x, y) = name;

Static constructors

Useful for initializing any static fields associated with a type (or any other per-type operations) - useful in particular for reading required configuration data into readonly fields.

class Ex
{
    // Static variable that must be initialized at run time.
    static readonly long baseline;

    // Static constructor is called at most one time, before any
    // instance constructor is invoked or member is accessed.
    // Can be a performance hit...
    static Ex()
    {
        baseline = DateTime.Now.Ticks;
    }

    public Ex() {}
}

Finalizers

Used to be known as destructors. Automatically called when class instance is being cleaned up by GC. Use SafeHandle or derived classes to wrap unmanaged handles.

As the implementer of a class, if you hold managed resources that ought to be disposed, you implement Dispose. If you hold native resources, you implement both Dispose and Finalize, and both call a common method that releases the native resources.

If user forgot to call Dispose and if the class have Finalize implemented then GC will make sure it gets called.

Structs

Structs can be more performant when <= 16 bytes. Avoid if they will be passed in to a method expecting an interface (requires boxing).

Data Structures

Dictionary

Initialization:

// Calls Add() for each entry.
var dict = new Dictionary<int, string>()
{
    { 101, "A" },
    { 102, "B" },
    { 103, "C" }
};

// Calls [] for each entry.
var dict = new Dictionary<int, string>()
{
    [101] = "A",
    [102] = "B",
    [103] = "C"
};

Other methods:

d.TryGetValue(key, out var value);
d.Keys;
d.Values;
d.Remove(key);
d.Clear();

Stack

s.Push(x);
var top = s.Peek();
var top = s.Pop();
var has = s.TryPeek(out var top);
var has = s.TryPop(out var top);

Queue

q.Enqueue(x);
var x = q.Peek();
var x = q.Dequeue();
var has = q.TryPeek(out var x);
var has = q.TryDequeue(out var x);

LinkedList

Doubly-linked

var node = new LinkedListNode<T>(val);

var node = l.First();
var node = l.Last();
l.AddFirst(node);
l.AddLast(node);
l.Remove(node);
l.RemoveFirst();
l.RemoveLast();
var node = l.Find(val);
var node = l.FindLast(val);

PriorityQueue

Min-heap

var pq = new PriorityQueue<TVal, TPriority>();
pq.Enqueue(val, priority);
var val = pq.Peek();
var val = pq.Dequeue();
var val = pq.EnqueueDequeue(val, priority);

var has = pq.TryPeek(out var val, out var priority);
var has = pq.Dequeue(out var val, out var priority);

Sorted Collections

https://stackoverflow.com/questions/935621/whats-the-difference-between-sortedlist-and-sorteddictionary

LINQ

IEnumerable Interfaces

IEnumerable<T> just has a GetEnumerator() method that returns an IEnumerator<T> which has

static IEnumerable<T> ZeroOne(T x)
{
    yield break;
    yield return x;
}

IQueryable

public interface IQueryable : IEnumerable
{
 Type ElementType { get; }
 Expression Expression { get; }
 IQueryProvider Provider { get; }
}

Properties:

Func<int, int, int> function = (a,b) => a + b; // lambda
Expression<Func<int, int, int>> expression = (a, b) => a + b; // expression tree

The expression tree consists of four main properties:

https://blexin.com/en/blog-en/linq-in-depth-advanced-features/

Syntax

var querySyntax =
 from customer in customers
 let years = GetYears(customer) // nice!
 where years > 5
 orderby years
 select customer.Name;
  
var methodSyntax = customers
 .Select(customer =>  //anonymous type
  (
     Years: GetYears(customer),
     Name: customer.Name
  )
 .Where(x => x.Years > 5)
 .OrderBy(x => x.Years)
 .Select(x => x.Name);

Methods

xs.Aggregate(0, (a, x) => a+x);

// deferred execution, unlike ToLookup
var results = persons.GroupBy(
    p => p.PersonId, // key
    p => p.car, // elements
    (key, g) => new { PersonId = key, Cars = g.ToList() }); // results

Parallelism

A lock allows only one thread to invoke an operation at a time. A semaphore allows a predefined number of threads (specified in code) to invoke the method at a time.

Threading

Interlocked class provides methods to performing atomic operations on shared variables.

[ThreadStatic] attribute can be added to static fields. The CLR creates isolated versions of the same variable in each thread. The fields are created on Thread Local Storage so every thread has it own copy of the field i.e the scope of the fields are local to the thread.

https://stackoverflow.com/questions/5227676/how-does-the-threadstatic-attribute-work

Thread.CurrentThread.IsBackground

Locks

A lock is specific to the AppDomain, while Mutex to the Operating System allowing you to perform inter-process locking and synchronization (IPC). Lock is built with mutexes: prefer lock if possible.

Sync primitives in System.Threading:

async Task WorkerMainAsync()
{
    var sema = new SemaphoreSlim(10);
    var tasks = new List<Task>();
    while (DoMore())
    {
        // WaitAsync produces a task that will be completed when that thread has been given "access" to that token.
        // await-ing that task lets the program continue execution when it is "allowed" to do so. 
        await sema.WaitAsync();
        tasks.Add(Task.Run(() =>
        {
            DoWork();
            sema.Release();
        }));
    }
    await Task.WhenAll(tasks);
}


// Enter semaphore by calling Wait or WaitAsync
SemaphoreSlim.Wait()
//  Execute code protected by the semaphore.
SemaphoreSlim.Release()

Task Parallel Library (TPL)

Simplifies the process of adding parallelism and concurrency to applications. The TPL:

A task resembles a thread or ThreadPool work item but at a higher level of abstraction. Tasks provide two primary benefits:

TPL is the preferred API for writing multi-threaded, asynchronous, and parallel code in .NET.

Comparison to threads:

TODO merge with rest

var tnew = new Task(() => { ... });
tnew.Start();

var trun = Task.Run(() => { ... });

Task.WaitAll(tnew, trun);

// Exceptions are propagated when you use one of the static or instance Task.Wait methods

// new eg
Tast<string>[] tasks = urls.Select(url => DownloadStringAsync(url)).ToArray();
try
{
    string[] pages = await Task.WhenAll(tasks);
    ...
}
catch(Exception)
{
    foreach(var faulted in tasks.Where(t => t.IsFaulted))
    {
        ... // work with faulted and faulted.Exception
    }

// alternatively, in sync code:
Task t = Task.WhenAll(tasks);
t.Wait(); // r.Result();
}

Creating and running tasks:

// Creating and running tasks implicitly
Parallel.Invoke(() => DoWork(), () => DoWork2()); // both run concurrently

/// Always uses the default task scheduler
Task t = Task.Run( () => Console.WriteLine("hi"));
t.Wait();

Task t = new Task( () => Console.WriteLine("hi"));
t.Start();
Console.WriteLine($"{t.Status}");
t.Wait(); // to ensure that the task completes execution

// Method to create and start a task in one operation
// Can use to pass state into a task, that can be retrieved through Task.AsyncState
Task.Factory.StartNew(
    (Object o) => ((Target)o).Value = 5,
    new Target(value: 2) );
// Note when using lambdas, they can capture outer state.

// The typed version Factory has a .Result property
var tasks = {
                Task<Double>.Factory.StartNew(() => DoCalc(1)),
                Task<Double>.Factory.StartNew(() => DoCalc(9))
            };

var sum = tasks.Sum(t => t.Result);

TaskCreationOptions

Creating task continuations:


var getData = Task.Factory.StartNew( () => new[] { 42, 5, 9 });
var sumData = getData
    .ContinueWith( (x) => x.Result.Sum() )
    .ContinueWith( (x) => Console.WriteLine(x.Result));
sumData.Wait();

// ContinueWhenAll
// ContinueWhenAny 

// can use TaskContinuationOptions.OnlyOnFaulted to log exceptions

Creating detached child tasks:

var parent = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Parent task beginning.");
    // Child isn't synchronized to parent
    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000);
        Console.WriteLine("Detached child task completed.");
    });
});
parent.Wait(); // doesn't wait for child

Creating child tasks (to express structured task parallelism):

var parent = Task.Factory.StartNew(() =>
{
    Console.WriteLine("Parent task beginning.");
    // Child isn't synchronized to parent
    var child = Task.Factory.StartNew(() =>
    {
        Thread.SpinWait(5000);
        Console.WriteLine("Detached child task completed.");
    }, TaskCreationOptions.AttachedToParent);
});
parent.Wait(); // waits for all attached child tasks to finish

Waiting for tasks to finish by blocking synchronously:

Wait will throw any exceptions raised by a task, even if the task is completed. Typically, you would wait for a task for one of these reasons:

Others

Exception handling:

All exceptions are wrapped in an AggregateException and propagated back to the thread that joins with the task. Using Wait*, Result in a try/catch can allow you to handle the exception. The joining thread can also handle exceptions by accessing the Exception property before the task is GCd. By accessing this property, you prevent the unhandled exception from triggering the exception propagation behavior.

Task cancellation:

static async Task Main()
{
    var tokenSource = new CancellationTokenSource();
    CancellationToken ct = tokenSource.Token;

    var task = Task.Run(() =>
    {
        // Were we already canceled?
        ct.ThrowIfCancellationRequested();

        while (true)
        {
            // Poll on this property if you have to do
            // other cleanup before throwing.
            if (ct.IsCancellationRequested)
            {
                // Clean up here, then...
                ct.ThrowIfCancellationRequested();
            }
        }
    }, ct); // Pass same token to Task.Run.

    // Actually cancel...
    tokenSource.Cancel();

    // Just continue on this thread, or await with try-catch:
    try
    {
        await task;
    }
    catch (OperationCanceledException e)
    {
        Console.WriteLine($"{nameof(OperationCanceledException)}: {e.Message}");
    }
    finally
    {
        tokenSource.Dispose();
    }
}

// TaskCanceledException : OperationCanceledException

Task.ConfigureAwait(continueOnCapturedContext)

By passing false we indicate that we want to continue the rest of the method on the thread pool instead of the main thread. This is safe as long as you don’t later do anything that requires the main thread. Enables some parallelism. Library authors are encouranged to use false, else can cause deadlocks.

TODO:

Async / await

await

The async keyword only enables the await keyword (and manages the method results).

The beginning of an async method is executed just like any other method, i.e., it runs synchronously until it hits an “await” (or throws an exception). The “await” keyword is where things can get asynchronous. Await is like a unary operator: it takes a single argument, an awaitable (an “awaitable” is an asynchronous operation). I like to think of “await” as an “asynchronous wait”. That is to say, the async method pauses until the awaitable is complete (so it waits), but the actual thread is not blocked (so it’s asynchronous).

https://blog.stephencleary.com/2012/02/async-and-await.html

It is the type that is awaitable, not the method returning the type (so can await a non-async method that returns Task). An awaitable type is one that includes at least a single instance method called GetAwaiter() which retrieves an instance of an awaiter type. https://devblogs.microsoft.com/pfxteam/await-anything/

Captured Context

Used to sync back to the main context. The context is later applied to the remainder of the async method. What is context?

Simple answer:

Complex answer:

The Context can determine whether we resume on the same thread. A UI context will generally resume on the same thread.

Can avoid context using ConfigureAwait(false) which allows it to run on the threadpool context.

private async Task DownloadFileAsync(string name)
{
  var c = await DownloadFileContentsAsync(name).ConfigureAwait(false);

  // Because of ConfigureAwait(false), we are not on the original context here.
  // Instead, we're running on the thread pool.
  // If it was true (default), then it has to re-enter the same context before proceeding.

  await WriteToDiskAsync(name, c).ConfigureAwait(false);
  // The second call to ConfigureAwait(false) is not *required*, but it is Good Practice.
}

private async void DownloadFileButton_Click(object sender, EventArgs e)
{
  // UI context
  await DownloadFileAsync(fileNameTextBox.Text);

  // Resume on the UI context, we can directly access UI elements.
  resultTextBox.Text = "File downloaded";
}
Blocking Non-Blocking
task.Wait await task
task.Result await task
Task.WaitAny await Task.WhenAny
Task.WaitAll await Task.WhenAll
Thread.Sleep await Task.Delay
Task constructor Task.Run,TaskFactory.StartNew

Guidelines

https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/master/AsyncGuidance.md

If a method is declared async make sure there is an await.

Async method shouldn’t return void, return Task if nothing to return. Reasons:

Consider using return Task instead of return await:

public async Task<string> AsyncTask()
{
    // non-async stuff
    return await GetData();
}

public Task<string> JustTask()
{
    // non-async stuff
    return GetData();
};

This avoids the generation of the (somewhat expensive) async state machine. However, do not wrap return Task inside try-catch or using block as an exception thrown by the async method will never be caught, because the task will be returned right away.

Adding async to function declaration automatically wraps the return with task [check]. Use await to wait for a task to complete, and get the unwrapped result. e.g. await Task.Run(() => DoStuff());

Use .GetAwaiter().GetResult() instead of .Wait() or .Result: simplifies error-handling as the latter will return an AggregateException.

Async library methods should consider using Task.ConfigureAwait(false) to boost performance. Synchronization context represents a way to return to the original context of the code: whenever a Task is awaited, it captures current (thread) synchronization context before awaiting, but this is usually not needed when writing library code. When Task.ConfigureAwait(false) is used, the code, if possible, avoids this context switch and tries to complete in the thread that completed the task.

Channels

Like a multiple-reader multiple-writer thread-safe queue. Lets us communicate between 2 (or more) concurrent operations.

var channel = Channel.CreateBounded<Person>(10);
var tasks = new List<Task>();

// An async job that writes results to the channel
async Task Get(int id)
{
  var person = await GetPersonAsync(id);
  await channel.Writer.WriteAsync(person);
}

foreach (var id in ids)
    tasks.Add(Get(id));

// Wait for the async tasks to complete & close the channel // non-blocking
Task.Run(async () =>
{
    await Task.WhenAll(tasks); // blocks
    channel.Writer.Complete(); // closes channel
});

// Read person objects from the channel until the channel is closed
// OPTION 1: Using WaitToReadAsync / TryRead
while (await channel.Reader.WaitToReadAsync()) // false if channel is closed
while (channel.Reader.TryRead(out var person)) // false if no items left
    Console.WriteLine($"{person.ID}");

// IAsyncEnumerable<Person>
// OPTION 2
await foreach (var person in channel.Reader.ReadAllAsync())
    Console.WriteLine($"{person.ID}");

Working with nulls

Coalescing

// Null-coalescing operator
var x = p ?? valueIfNull;
var x = p ?? throw new ArgumentNullException();

// Null-conditional operators
var x = p?.k; // x will be null if p is null
var x = p?[k];

Equality

(x == null); // `==` can be overloaded
(x is null);

Newer Roslyn compilers make the behavior of the two operators the same when there is no overloaded equality operator.

Functions

Local functions double square(double x) { return x; }

Expression Lambda (input-parameters) => expression Func<int, int> square = x => x * x;

Statement Lambda (input-parameters) => { }

Action<string> greet = name => {
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};

Exceptions

Catch multiple exceptions: catch (Exception ex) when (ex is ... || ex is ... )

IO

Files

File.WriteAllText(path, text);

using (var file = new StreamWriter(path, append))
  file.WriteLine(line);

// Same as new StreamWriter(,append:false,encoding:UTF-8)
using (var streamWriter = File.CreateText(path))
  file.WriteLine(line);

Performance

Span<byte> bytes = stackalloc byte[length];
Span<T> CollectionsMarshal.AsSpan<T>(List<T> list);
Span<TTo> MemoryMarshal.Cast<TFrom,TTo> (Span<TFrom> span);

var xs = new int[] {1,2,3};
var mem = new Memory<int>(xs);
xs.Slice(1,2);
var s = xs.Span();
s[0], s[2] // can't index into Memory yet

SIMD

// create a SIMD vector from an array, starting at index
// vector will have length Vector.Count (depends on machine)
new Vector(T[] values, int index);

GC

GCSettings.LatencyMode enum can instruct the GC to prioritize latency.

Reflection

var parameters = new object[] {};
var instance = Activator.CreateInstance(typeof(T), parameters);

var assembly = Assembly.LoadFrom(assemblyPath);
var methods = assembly.GetExportedTypes()
        .SelectMany(t => t.GetMethods());

Unsafe

Keywords:

Passing pointers between methods can cause undefined behavior.

Function pointers can be defined using delegate*.

unsafe void Fun()
{
    double[] arr = { 0.0, 1 };
    string str = "Hello";

    // can't point to a reference or to a struct that contains references
    // unless you use fixed
    fixed (double* p = arr) { /*...*/ }
    // is the same as
    fixed (double* p = &arr[0]) { /*...*/ }

    fixed (char* p = str) { /*...*/ }
}

unsafe
{
    int i = 5;
    int* p = &p;
    *p *= *p; // squares
}

internal unsafe struct Buffer
{
    // stored on the type, i.e. not a reference
    public fixed char fixedBuffer[8];
}

Tests

NUnit

[Test]
[TestCase(6, 13)]
[TestCase(3, 10)]

Misc

Type aliases

using Alias = Namespace.Target;

This can be used to keep consistent names in source when using an external lib, using shorter more familiar names etc.

Advanced Features

Source Generators

A dynamic templating engine that produces C# source code at compile-time. Provides access to the syntax tree which enables compile-time access to information about the code such as members of a class, names and types of members etc. Can provide compile-time reflection which can allow much higher performance including ahead-of-time compilation and linking support.

C Interop

To wrap a C++ dll

// TODO: check this

C# can only call C functions with a certain calling convention (I think stdcall?) C++ has a different ABI than C (due to name mangling). In short, C is the common denominator between C# and C++ so you need to have a layer of abstraction between C# and C++ in C.

You basically have to go C++ -> C -> C#, so you need:

JSON

using System.Text.Json;

var options = new JsonSerializerOptions { WriteIndented = true };
string jsonString = JsonSerializer.Serialize(dto, options);

Dto? dto = JsonSerializer.Deserialize<Dto>(jsonString);

Packages (third-party)

Json.NET

How to de/serialize using custom JsonConverter with a manual step while still getting automatic de/serialization otherwise. Also, a nice showcase of C#’s verison of defer (a la go, zig, etc).

https://stackoverflow.com/questions/29719509/json-net-throws-stackoverflowexception-when-using-jsonconvert

Other

Performance optimisation strategies of the last resort https://stackoverflow.com/q/926266/3142827

What and where are the stack and heap? https://stackoverflow.com/q/79923/3142827

Storing variables on the Stack vs Heap (value vs ref types) https://stackoverflow.com/a/1114152/3142827

References