Interesting notes on C # and CLR

Studying the C # programming language, I came across features of both the language itself and its runtime, * some of which, so to speak, are "widely known in narrow circles." Gathering them day after day in his piggy bank in order to ever repeat what honestly I have never done before, I got the idea to share them.

These notes will not make your code more beautiful, faster and more reliable, there is Steve McConnell for this. But they will definitely contribute to your way of thinking and understanding.

Some of the below will seem too simple, another, on the contrary, difficult and not necessary, but without it.

So, we begin:

1) The location of objects and instances in dynamic memory

Objects contain static fields and all methods. Instances only contain non-static fields. This means that the methods are not duplicated in each instance, and the Flyweight pattern is used here.

2) Passing parameters to methods

The structure passes its copy to the method, the class passes a copy of its reference. But when we use the REF keyword, the structure passes a pointer to itself, and the class passes its original link.
Пример REF для ссылочного типа.
1) Работает без ошибок, мы зануляем копию переданной ссылки.
static void Main( string[] args )
      StringBuilder sb = new StringBuilder();
      sb.Append("Hello ");
private static void AppendHello(StringBuilder sb)
      sb.Append(" World!");
      sb = null;
2) Возникает исключение System.NullReferenceException при попытке обратиться к методу в переменной значение которой null.
static void Main( string[] args )
      StringBuilder sb = new StringBuilder();
      sb.Append("Hello ");
      AppendHello(ref sb);
private static void AppendHello(ref StringBuilder sb)
      sb.Append(" World!");
      sb = null;

3) Prepare the code before execution

The CLR has a CER block, which tells JIT to “prepare the code before execution, so when you need it, everything will be at hand.” For this, we connect the System.Runtime.CompilerServices and RuntimeHelpers.PrepareConstrainedRegions namespaces.

4) Regular expressions

Regex can be created with the Compiled option - this generates an expression in IL code. It is much faster than usual, but the first launch will be slow.

5) Arrays

One-dimensional arrays in IL are represented by a vector; they work faster than multidimensional ones. Arrays of one-dimensional arrays use vectors.

6) Collections

It is better to inherit custom collections from ICollection, the implementation of IEnumerable is free. But there is no index (very individual).

7) Extensive methods

If the name of the extension method conflicts with the name of the type method, then the full name of the extension method can be used, and the type can be passed as an argument.

StaticClass.ExtesionMethod( type );


LINQ lazy loading - select, where, take, skip etc.
LINQ eager loading (requests are executed immediately) - count, average, min, max, ToList etc. (But if the collection is infinite, then the request will never end.)

9) Sync block

Structural types and primitives (byte, int, long ...) do not have a synchronization block, which is present in objects in the managed heap along with the link. Therefore, the Monitor. () Or Lock () construct will not work.

10) Interfaces

If in C # the name of the interface in which this method is defined (IDisposable.Dispose) is indicated before the method name, then you create an explicit implementation of the interface method (Explicit Interface Method Implementation, EIMI). If you explicitly implement the interface method in C #, you cannot specify the access level (open or closed). However, when the compiler creates metadata for the method, it assigns it a private access level, which prevents any code from using the class instance by simply calling the interface method. The only way to invoke the interface method is to access through a variable of this interface type.

EIMI is indispensable (for example, when implementing two interface methods with the same names and signatures).

11) Not in C #, but IL is supported

Static fields in interfaces, methods that differ only in return value, and much more.

12) Serialization

When serializing a graph of objects, some types may be serializable, and some may not. For performance reasons, the formatting module does not check the possibility of this operation for all objects before serialization. This means that a situation may arise when some objects are serialized into the stream before the SerializationException occurs. As a result, corrupted data is in the I / O stream. This can be avoided, for example, by serializing objects first in a MemoryStream.

In C #, inside types marked with the [Serializable] attribute, you should not define automatically implemented properties. The fact is that field names generated by the compiler can change after each subsequent compilation, which will make it impossible to deserialize type instances.

13) Constants

Constants are placed in assembly metadata, so if there have been changes, you need to recompile all assemblies using it. Because A DLL with a constant may not even load.
It is better to use static readonly setting values ​​in the constructor, it is constantly loaded in the assemblies using it, and returns the actual value.

14) Delegates

GetInvocationList - returns a chain of delegates, you can call any, catch exceptions, get all the return values, and not just the last one.

15) String Comparison

In Microsoft Windows, string comparison in uppercase is optimized. * StringComparison.OrdinalIgnoreCase, in fact, converts Char to uppercase. ToUpperInvariant. We use (). Windows by default uses UTF-16 encoding.

16) Optimization for multiple lines

If in an application strings are often compared by case-sensitive comparison, or if the application expects many identical string objects to appear, then to improve performance, you must use the string interning mechanism supported by the CLR.

Upon initialization, the CLR creates an internal hash table in which the keys are strings and the values ​​are references to string objects in the managed heap.

17) Safe strings

When creating a SecureString object, its code allocates a block of unmanaged memory that contains an array of characters. The garbage collector does not know anything about this unmanaged memory. This memory needs to be cleared manually.

18) Security

Managed assemblies always use DEP and ASLR.

19) Method Design

When declaring the type of method parameters, it is necessary to specify “minimal” types, preferring interfaces to base classes. For example, when writing a method that works with a set of elements, it is best to declare a method parameter using the IEnumerable interface.

public void ManipulateItems<T>(IEnumerable<T> collection) { ... }

At the same time, declaring the type of the object returned by the method, it is desirable to choose the strongest of the available options (trying not to be limited to a specific type). For example, it is better to declare a method that returns a FileStream object rather than a Stream.

20) Once again about car properties

It is better not to use the automatically implemented properties of AIP (author’s opinion, guess which one).
a) The default value can only be set in the constructor. (Changed in Roslyn C # 6);
b) The problem with serialization (paragraph 12);
c) You cannot set a breakpoint.

21) configuration file

a) Any binary .NET code can be associated with an external XML configuration file. This file is located in the same directory, and has the same name with the word .CONFIG added at the end;
b) If you provide the solution only in binary form, documenting comments can be compiled into an XML file during compilation, therefore, in this situation too, you can provide users with an excellent set of tips. To do this, you only need to place the resulting XML file in the same directory as the binary, and Visual Studio .NET will automatically display comments in IntelliSense prompts.

22) Exceptions

CLR resets the start point of the exception:

try {} catch (Exception e) { throw e; }

The CLR does not change information about the start point of the exception:

try {} catch (Exception e) { throw; }

You can raise the FirstChanceException event of the AppDomain class and receive information about exceptions before the CLR starts looking for their handlers.

Exceptions work slowly because There is a transition to kernel mode.

23) IS and AS

IS - In this code, the CLR checks the object twice:

if ( obj is Person ) { Person p = (Person) obj; }

AS - In this case, the CLR checks for obj compatibility with the Person type only once:

Person p1 = obj as Person; if ( p1 != null ) { ... }

24) Check if there is enough memory before executing

Creating an instance of the MemoryFailPoint class checks to see if there is enough memory before starting the action or throw an exception. However, keep in mind that physically memory has not yet been allocated, and this class cannot guarantee that the algorithm will receive the necessary memory. But its use will definitely help make the application more reliable.

25) A bit about Null

To use null compatible Int32 you can write:

Nullable<Int32> x = null; или Int32? x = null;

The union operator of null compatible values ​​is ?? (if the left operand is null, the operator proceeds to the next one), consider two equivalent expressions:
string temp = GetFileName(); 
string fileName = ( temp != null ) ? temp : "Untitled";

string fileName = GetFileName() ?? "Untitled";

26) Timers

FCL library contains various timers:
1) Timer in System.Threading - suitable for performing background tasks in the pool thread;
2) Timer in System.Windows.Forms - the timer is associated with the calling thread, this prevents a parallel call;
3) DispatcherTimer in System.Windows.Threading. - equivalent to the second, but for Silverlight and WPF applications;
4) Timer in System.Timers. - In fact, is the shell of the first, Jeffrey Richter does not advise using it.

27) Type and typeof

To get a Type instance for types, the typeof operation is used instead of the Type.GetType method. If the type is known at compile time, then the typeof operation immediately searches for the method instead of doing it at run time.

28) using chip

To reduce the amount of code and make it clearer? you can use the using directive as follows:
using DateTimeList = System.Collections.Generic.List <System.DateTime>;

29) Preprocessor Directives

a) #IF DEBUG and #ENDIF - are used to indicate blocks of code that will be compiled only in DEBUG mode.
b) #DEFINE XXX; #IF (DEBUG && XXX) - you can add the assembly number “XXX” to the condition.
c)! DEBUG == RELEASE (just in case).
d) #LINE 111 - in the error window will show line 111.
e) #LINE HIDDEN - hides the line from the debugger.
f) #WARNING XXX; #ERROR YYY - means XXX - warning, YYY - error.

30) Any * antiquity

Covariance - conversion in the direct order, keyword OUT.

string[] strings = new string[3];
object[] objects = strings;
interface IMyEnumerator<out T>
    T GetItem( int index );
    T --> R
IOperation<T> --> IOperation<R>

Contravariance - conversion in the reverse order, keyword IN.

interface IMyCollection<in T>
    void AddItem( T item );
    R --> T
IOperation<T> --> IOperation<R>

Invariance - Implicit type conversion is not allowed.

By default, generic types are invariant. Still generalized classes are called open, in runtime they are closed by specific types of "int", "string", for example. And these are different types, the static fields in them will also be different.

31) Extension Methods

public static class StringBuilderExtensions { 
    public static Int32 IndexOf ( this StringBuilder sb, Char char) { ... } 

Allows you to define a static method that is invoked by the instance method syntax. For example, you can define your own IndexOf method for StringBuilder. First, the compiler will check the StringBuilder class or all its base classes for the presence of the IndexOf method with the necessary parameters, if it does not find one, it will look for any static class with a specific IndexOf method, whose first parameter corresponds to the type of expression used when the method is called.

And this is the implementation of the Visitor pattern in .Net.

32) Execution Contexts

Each thread has a specific execution context. It includes security settings, host parameters, and logical call context data. By default, the CLR automatically copies it, from the very first thread to all the auxiliary ones. This guarantees the same security settings, but to the detriment of performance. To control this process, use the ExecutionContext class.

33) Volatile

JIT - the compiler guarantees that access to the fields marked with this keyword will occur in volatile read or write mode. It forbids the C # compiler and the JIT compiler to cache the contents of the field in the processor registers, which guarantees that during all read and write operations, manipulations will be performed directly with the memory.

34) Collection classes for parallel thread processing

ConcurrentQueue - processing elements according to the FIFO algorithm;
ConcurrentStack - processing elements using the LIFO algorithm;
ConcurrentBag - an unsorted set of elements that allows duplication;
ConcurrentDictionary <TKey, TValue> - an unsorted set of key-value pairs.

35) Streams

User-mode constructs:
a) Volatile constructions - an atomic read or write operation.
VolatileWrite, VolatileRead, MemoryBarrier.
b) Interlocking structures - an atomic read or write operation.
System.Threading.Interlocked (interlocking) and System.Threading.SpinLock (locking with looping).
Both constructions require passing a link (address in memory) to a variable (recall structures).

Kernel-mode constructions:
About 80 times slower than user-mode constructions, but they have a number of advantages described in MSDN (a lot of text).

Class hierarchy:


36) Class Fields

Do not initialize the fields explicitly, do this in the default constructor, and use it with the this () keyword for constructors that take arguments. This will allow the compiler to generate less IL code, as initialization occurs 1 time in any of the constructors (and not all of them have the same values, copies).

37) Running only one copy of the program

public static void Main ()
    bool IsExist;
    using ( new Semaphore ( 0, 1, "MyAppUniqueString", out IsExist ) ) {
    if ( IsExist ) { /* Этот поток создает ядро, другие копии программы не смогут запуститься. */ }
    else { /* Этот поток открывает существующее ядро с тем же именем, ничего не делаем, ждем возвращения управления от метода Main, что бы завершить вторую копию приложения. */  }

38) Garbage collection

Two modes are implemented in the CLR:
1) Workstation - the collector assumes that other applications do not use processor resources. Modes - with and without parallel assembly.
2) Server-collector assumes that no third-party applications are running on the machine, all CPU resources for assembly! The managed heap is parsed into several sections - one per processor (with all the consequences, i.e. one thread per heap).

39) Finalizer

It fulfills the function of the object’s last desire before deletion, cannot last longer than 40 seconds, do not play with it. Translates the object to at least 1 generation, because not deleted immediately.

40) Monitoring and managing the garbage collector at the facility

We call the static Alloc method of the GCHandle object, pass a reference to the object and the GCHandleType type in which:
1) Weak is a monitor, we find that the object is no longer available, the finalizer could be executed.
2) WeakTrackResurrection - monitoring, we find out that the object is no longer available, the finalizer was exactly executed (if any).
3) Normal - control, makes you leave the object in memory, the memory occupied by this object can be compressed.
4) Pinned - control, forces you to leave the object in memory, the memory occupied by this object cannot be compressed (i.e. moved).

41) CLR

The CLR is essentially a processor for MSIL instructions. While traditional processors use registers and stacks to execute all instructions, the CLR uses only the stack.

42) Recursion

If you ever saw an application that paused for another second and then completely disappeared without any error message, it was almost certainly caused by endless recursion. Stack overflow, as you know, cannot be intercepted and processed. Why? We read in books or blogs.

43) Windbg and SOS (Son of Strike)

How many domains are present in the process at once?
- 3. System Domain, Shared Domain and Domain 1 (the domain with the current application code).

How many heaps (generations) actually?
- 0, 1, 2 and Large Object Heap.
Large Object Heap - for very large objects, it is not compressed by default, only through the configuration in the configuration XML file.

Another difference in the client and server mode of garbage collection (in books not everything is so detailed, translation inaccuracy is possible).
- for each core, its own HEAP is created, each of which has its own 0, 1, 2 generations and Large Object Heap.

Creating an array larger than 2 GB on 64 bit platforms.
- gcAllowVeryLargeObjects enabled = "true | false"

What to do when there is free memory, but it is impossible to allocate a large continuous section for a new object?
- enable compact mode for Large Object Heap. GCSettings.LargeObjectHeapCompactionMode;
It is not recommended to use, it is very expensive to move large objects in memory.

How fast in runtime to find thread loops (dead-locks)?
-! Dlk

Sources (not advertising):
1) Jeffrey Richter, “CLR via C #” 3rd / 4th edition.
2) Trey Nash, "C # 2010 Crash Course for Professionals."
3) John Robbins, "Debugging Applications for Microsoft .NET and Microsoft Windows."
4) Alexander Shevchuk (MCTS, MCPD, MCT) and Oleg Kulygin (MCTS, MCPD, MCT) ITVDN resource (
5) Sergey Pugachev. Microsoft engineer (

I hope this list has been enjoyed by both beginners and experienced C # programmers.

Thanks for attention!

* Updated, fixed errors, supplemented some points with examples.
If you find a mistake, please report this in a personal message.

Also popular now: