.NET
C# vs .NET
C# is not the only language we can run under .NET, but in 99% of the cases it runs under .NET. .NET is a framework that enables running applications wrtitten in C#.
Some other .NET-compatible programming languages are F#, IronPython, VB etc.. The cool thing is that we can use the code written in one language in the code wrtitten in another. For example, we could have a C# class derived from a F# class. We can think of C# as airplane and .NET as airport.
.NET
Its most important part is the execution environment called Common Language Runtime, which is responsible for managing the memory, providing error handling, dealing with threads and more. .NET also provides a set of standard libraries, such as System.Linq, System.Collections and many more (those System libraries). 可以把.net理解为java世界里的JRE,which includes JVM(和CLR)很像,还有一些标准库。
.NET可以理解为一个平台,这个平台有三大支柱:
1. The Runtime。包含CLR。保证程序能跑起来。
2. The SDK。包含编译器,命令行工具。保证程序能造起来。
3. The standard libraries。包含那些System libraries等。不用重新发明轮子,拿来就能用。
.NET-related frameworks
Windows Forms / Windows Presentation Foundation (WPF): create desktop applications
ASP.NET MVC: web development
.NET MAUI: Cross-platform framework for creating native mobile and desktop apps.
.NET (Core) vs .NET Framework
时代背景: .NET Framework 诞生时,微软的策略是“全家桶绑定 Windows”。它被深度集成在 Windows 操作系统里。
挑战: 随着云计算(Linux 服务器)和移动端兴起,.NET Framework 太胖了,且离不开 Windows。
新生: 微软推倒重来,写了一个全新的、轻量级的、跨平台的引擎,最初叫 .NET Core。
统一: 到了 2020 年,微软觉得“.NET Core”和“.NET Framework”名字太乱,直接把后续版本统一命名为 .NET(跳过版本号 4,直接从 .NET 5 开始)。
C# Compilation
When we click Build in Visual Studio, the C# compiler called Roslyn starts its work. It takes all the *.cs files from the project and compiles them into the Common Intermediate Language code (exe, dll). When we start the application, the CIL is translated to the binary code by JIT compiler, which is part of the Runtime. We call this process just-in-time comlilation because a piece of code is only compiled to the binary code right before it is used for the first time. This is specific to the machine the program runs on so the same app can be run on different systems like Windows or Mac OS. The binary code must be slightly different for those systems, and the just-in-time compiler takes care of that. That’s why using .NET, we can easily create apps that can be run on different platforms.
Another benefit of the JIT compilation is that when it is used, not all code is necessarily compiled into binary code. It is often the case that big chunks of an application are not actually executed when it runs. The user may use one simple feature and then close the application, so compiling it all to the binary code would be a waste of time and resources.
All .NET-compatible languages, not only C# get compiled to the CIL. That enables the communication between a C# and F# libraries. For example, we can have a C# class derived from an F# class because they both get compiled into the same programming language – the Common Intermediate Language.
The JIT compilation is managed by the Common Language Runtime.
Common Language Runtime (CLR)
It is a runtime environment that manages the execution of .NET applications. It’s responsibilities include:
– JIT compilation
– Memory management
– Error handling
– Thread management
Memory
Stack: One per thread. It occupies 1MB of memory for 32-bit processes and 4 MB for 64-bit processes.
Heap: Single entity shared between all threads.
Try to not modify the input parameter in a method.
Gargabe Collector
GC will not clean the memory immediately. We can’t deterministically say when exactly that will happen. The triggers of garbage collector is:
– When the operating system informs the CLR that it has little free memory left.
– When the amount of memory occupied by objects on the heap surpasses a given threshold. CLR continuously adjusts this threshold as the program runs.
– When the GC.Collect method is called.
– It only works when there is a need for it.
GC runs in background on its own, in a seperate thread. It may stop all other threads when working and it might cause performance challanges.
Memory Fragmentation/Defragmentation
Memory fragmentation: there are chunks of free memory placed between chunks occupied by some objects.
Memory Defragmentation: The process of moving the objects in memory to make them contiguous and create a bigger block of free memory.
Mark and Sweep/Reference Counting
GC finds the objects that can be removed using the Mark-and-Sweep algorithm bacause it first marks the objects that can be removed and then sweeps them from memory.
How can we tell if an object can be removed? There’re 2 algorithms:
– Reference Counting (used in Swift). The GC keeps track of the count of references pointing to some object. When the count reaches zero, it considers this object unreferenced by any other object, and thus a candidate to be removed from memory. But if there’s a circular referencing, we have a problem.
– Tracing (used in C#). Tracing will determine whether an object is reachable or not by tracing it from a set of application roots. If an object is reachable from any of the roots, it’s included in the graph of reachable objects. If it is not reachable, it can be removed. The power of this algorithm is that the circular references will not be included in the graph of reachable objects.
But what are application roots exactly?
– Static fields
– Local variables on the thread’s stack
– CPU registers
– Handle Table / GCHandles
注意:GC只清理heap,不会清理栈。因为stack的清理机制是自动且及时的。一旦stack frame被pop, stack frame里的一切value type和reference被瞬间销毁。这个过程不需要任何后台进程,它是随着代码的运行同步完成的。
Generations of Objects
If some object survives a couple of cycles of the GC’s work, it’s most likely long-lived. We should check it only every once in a while. Once an object is first created, it is assigned to generation 0. If it survives its first collection, it advances to generation 1, then advances to generation 2 if it survives again. The GC checks from generation 0 most frequently. Generation的值越大,GC就越不那么频繁的去检查。
In a well-tuned application most objects die in generation 0.
.NET把内存分为了三代:Gen 0(最年轻),Gen 1和Gen 2(最老)。During a collection of a generation, all previous generations are also collected. When generation 2 is collected, generations 0 and 1 are collected as well. And that’s why collecting objects from generation 2 is sometimes called a full garbage collection.
Objects that survived a couple of cycles of the GC’s work tend to be long-lived and they don’t need to be be checked upon so often. This way the GC has less work to do and performance is improved.
Memory Leaks
A memory leak is a situation when some pieces of memory is not being cleaned up, even if the object occupying it is no longer in use.The GC does not give us 100% protection from memory leaks.
One of the most common sources of memory leaks in .NET is related to events, the other is static fields. 比如说我们有一个static field来track所有该class的实例,即使这些实例已经不再被用到了,因为static field被当作application root, 所以它引用的所有的对象都被当做reachable,所以这些实例就不会被回收。所以最好在代码里慎用static field。