C# and .NET Fundamentals

Core concepts and syntax of C# and the .NET framework.

The CLR (Common Language Runtime) is the execution engine of the .NET platform that provides a managed execution environment for .NET applications. It's a crucial component that sits between your .NET code and the underlying operating system.

Key Components of the CLR:

  1. Just-In-Time (JIT) Compiler

    • Converts CIL (Common Intermediate Language) to native machine code
    • Optimizes code for the specific platform at runtime
    • Enables cross-platform execution
  2. Garbage Collector (GC)

    • Automatically manages memory allocation and deallocation
    • Prevents memory leaks and dangling pointers
    • Performs automatic cleanup of unused objects
  3. Type System

    • Enforces type safety and prevents type-related errors
    • Provides metadata about types, methods, and assemblies
    • Enables reflection and dynamic type inspection
  4. Security System

    • Implements Code Access Security (CAS)
    • Validates code permissions and execution rights
    • Provides sandboxing capabilities
  5. Exception Handling

    • Provides structured exception handling across languages
    • Ensures consistent error handling behavior
    • Supports stack unwinding and cleanup

Why the CLR is Important:

  1. Language Interoperability

    // C# code can use VB.NET assemblies and vice versa
    using VBProject;
    
    public class CSharpClass
    {
        public void UseVBNetClass()
        {
            var vbClass = new VBProject.VBNetClass();
            vbClass.DoSomething(); // Seamless interop
        }
    }
    
  2. Memory Management

    public class MemoryExample
    {
        public void DemonstrateGC()
        {
            // No need to manually free memory
            var largeObject = new byte[1000000];
            // GC automatically handles cleanup when object goes out of scope
        }
    }
    
  3. Type Safety

    public class TypeSafetyExample
    {
        public void DemonstrateTypeSafety()
        {
            int number = 42;
            // string text = number; // Compile-time error - type safety enforced
            string text = number.ToString(); // Explicit conversion required
        }
    }
    
  4. Cross-Platform Execution

    // Same C# code runs on Windows, Linux, macOS
    public class CrossPlatformExample
    {
        public void PlatformIndependentCode()
        {
            Console.WriteLine($"Running on: {Environment.OSVersion}");
            // Works on any platform with .NET runtime
        }
    }
    
  5. Performance Optimization

    public class PerformanceExample
    {
        public void JITOptimization()
        {
            // JIT compiler optimizes this code for the specific CPU
            for (int i = 0; i < 1000000; i++)
            {
                // Hot code gets optimized during execution
                ProcessData(i);
            }
        }
    }
    

CLR Execution Process:

  1. Compilation: Source code → CIL (Common Intermediate Language)
  2. Loading: CLR loads assemblies and metadata
  3. JIT Compilation: CIL → Native machine code
  4. Execution: Native code runs with CLR services
  5. Garbage Collection: Automatic memory management

CLR Versions and Evolution:

.NET VersionCLR VersionKey Features
.NET Framework 1.0CLR 1.0Initial release
.NET Framework 2.0CLR 2.0Generics, partial classes
.NET Framework 4.0CLR 4.0Dynamic language runtime
.NET Core 1.0CoreCLRCross-platform, modular
.NET 5+CoreCLRUnified platform

Benefits of CLR:

  1. Automatic Memory Management: No manual memory allocation/deallocation
  2. Exception Safety: Structured exception handling
  3. Security: Code access security and validation
  4. Performance: JIT compilation and optimization
  5. Interoperability: Language and platform independence
  6. Reliability: Type safety and runtime checks

CLR vs Native Code:

// CLR Managed Code
public class ManagedExample
{
    public void ManagedMethod()
    {
        // Automatic memory management
        var list = new List<int>();
        list.Add(1);
        // GC handles cleanup automatically
    }
}

// Unmanaged Code (P/Invoke)
public class UnmanagedExample
{
    [DllImport("kernel32.dll")]
    public static extern IntPtr GetCurrentProcess();
    
    public void UnmanagedMethod()
    {
        // Manual memory management required
        IntPtr handle = GetCurrentProcess();
        // Must manually free resources
    }
}

Key Takeaways:

  1. CLR is the execution engine that runs .NET applications
  2. Provides memory management, type safety, and security
  3. Enables language interoperability and cross-platform execution
  4. Optimizes performance through JIT compilation
  5. Essential for the .NET ecosystem and managed code execution

CIL (Common Intermediate Language), also known as MSIL (Microsoft Intermediate Language), is the intermediate language that all .NET languages compile to. It's a platform-agnostic, object-oriented assembly language that serves as the bridge between high-level .NET languages and the Common Language Runtime (CLR).

Key Characteristics of CIL:

  1. Platform Independent: CIL code can run on any platform with a .NET runtime
  2. Language Agnostic: All .NET languages compile to the same CIL format
  3. Object-Oriented: Supports classes, inheritance, polymorphism, and interfaces
  4. Stack-Based: Uses a stack-based execution model
  5. Strongly Typed: Enforces type safety at the intermediate language level

CIL Compilation Process:

Source Code (C#) → CIL → Native Code (JIT)
Source Code (VB.NET) → CIL → Native Code (JIT)
Source Code (F#) → CIL → Native Code (JIT)

Example: C# to CIL Translation

C# Source Code:

public class Calculator
{
    public int Add(int a, int b)
    {
        return a + b;
    }
    
    public static void Main()
    {
        var calc = new Calculator();
        int result = calc.Add(5, 3);
        Console.WriteLine(result);
    }
}

Equivalent CIL Code:

.class public auto ansi beforefieldinit Calculator
       extends [mscorlib]System.Object
{
  .method public hidebysig instance int32 Add(int32 a, int32 b) cil managed
  {
    .maxstack 2
    .locals init (int32 V_0)
    IL_0000: ldarg.1      // Load first argument (a)
    IL_0001: ldarg.2      // Load second argument (b)
    IL_0002: add          // Add the two values
    IL_0003: stloc.0      // Store result in local variable
    IL_0004: ldloc.0      // Load result
    IL_0005: ret          // Return the result
  }
  
  .method public hidebysig static void Main() cil managed
  {
    .entrypoint
    .maxstack 2
    .locals init (class Calculator V_0, int32 V_1)
    IL_0000: newobj instance void Calculator::.ctor()
    IL_0005: stloc.0
    IL_0006: ldloc.0
    IL_0007: ldc.i4.5
    IL_0008: ldc.i4.3
    IL_0009: callvirt instance int32 Calculator::Add(int32, int32)
    IL_000e: stloc.1
    IL_000f: ldloc.1
    IL_0010: call void [mscorlib]System.Console::WriteLine(int32)
    IL_0015: ret
  }
}

CIL Instruction Types:

  1. Load Instructions

    ldarg.0    // Load argument 0 (this)
    ldarg.1    // Load argument 1
    ldloc.0    // Load local variable 0
    ldc.i4.5   // Load constant integer 5
    
  2. Store Instructions

    stloc.0    // Store to local variable 0
    starg.1    // Store to argument 1
    
  3. Arithmetic Instructions

    add        // Addition
    sub        // Subtraction
    mul        // Multiplication
    div        // Division
    
  4. Control Flow Instructions

    br         // Unconditional branch
    brtrue     // Branch if true
    brfalse    // Branch if false
    ret        // Return
    
  5. Object Instructions

    newobj     // Create new object
    call       // Call method
    callvirt   // Call virtual method
    

CIL Metadata:

CIL assemblies contain rich metadata that describes:

// C# code with attributes
[Serializable]
public class Person
{
    [Required]
    public string Name { get; set; }
    
    [Range(0, 120)]
    public int Age { get; set; }
}

CIL Metadata includes:

  • Type definitions and inheritance hierarchies
  • Method signatures and implementations
  • Field definitions and properties
  • Custom attributes and annotations
  • Assembly references and dependencies

Benefits of CIL:

  1. Language Interoperability

    // C# can inherit from VB.NET classes
    public class CSharpClass : VBProject.VBNetBaseClass
    {
        // Seamless inheritance across languages
    }
    
  2. Platform Independence

    // Same CIL runs on Windows, Linux, macOS
    public class CrossPlatformClass
    {
        public void PlatformIndependentMethod()
        {
            // CIL ensures consistent behavior
        }
    }
    
  3. Optimization Opportunities

    public class OptimizationExample
    {
        public void OptimizedMethod()
        {
            // JIT compiler can optimize CIL based on runtime conditions
            for (int i = 0; i < 1000000; i++)
            {
                // Hot code gets aggressive optimization
            }
        }
    }
    
  4. Security and Verification

    public class SecurityExample
    {
        public void SafeMethod()
        {
            // CIL enforces type safety and security policies
            object obj = new string("test");
            // Type safety prevents dangerous operations
        }
    }
    

CIL vs Native Code:

AspectCILNative Code
PlatformPlatform-independentPlatform-specific
ExecutionJIT compiledDirect execution
SizeLarger (intermediate)Smaller (optimized)
StartupSlower (JIT overhead)Faster (no compilation)
OptimizationRuntime optimizationCompile-time optimization

Related Resources

The distinction between managed and unmanaged code is fundamental to understanding how .NET applications work and how they interact with system resources and external libraries.

Managed Code:

Managed code is code that runs under the control of the Common Language Runtime (CLR). The CLR provides automatic memory management, type safety, and other services.

Characteristics of Managed Code:

  1. Automatic Memory Management

    public class ManagedExample
    {
        public void ManagedMethod()
        {
            // Memory automatically allocated
            var list = new List<int>();
            list.Add(1);
            list.Add(2);
            // Memory automatically freed by Garbage Collector
        }
    }
    
  2. Type Safety

    public class TypeSafetyExample
    {
        public void SafeOperations()
        {
            int number = 42;
            // string text = number; // Compile-time error
            string text = number.ToString(); // Explicit conversion
            
            // Runtime type checking
            object obj = "Hello";
            if (obj is string str)
            {
                Console.WriteLine(str.ToUpper()); // Safe operation
            }
        }
    }
    
  3. Exception Handling

    public class ExceptionHandlingExample
    {
        public void ManagedExceptionHandling()
        {
            try
            {
                int result = Divide(10, 0);
            }
            catch (DivideByZeroException ex)
            {
                // Structured exception handling
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
        
        private int Divide(int a, int b)
        {
            return a / b; // Throws managed exception
        }
    }
    
  4. Security

    public class SecurityExample
    {
        public void SecureOperation()
        {
            // Code Access Security (CAS) applies
            // CLR validates permissions before execution
            File.WriteAllText("test.txt", "Hello World");
        }
    }
    

Unmanaged Code:

Unmanaged code runs directly on the operating system without the CLR's management. It's typically written in languages like C, C++, or assembly.

Characteristics of Unmanaged Code:

  1. Manual Memory Management

    // C code example
    #include <stdlib.h>
    
    void unmanaged_memory_example() {
        // Manual memory allocation
        int* numbers = malloc(100 * sizeof(int));
        
        // Use the memory
        for (int i = 0; i < 100; i++) {
            numbers[i] = i;
        }
        
        // Manual memory deallocation (must not forget!)
        free(numbers);
    }
    
  2. Direct System Access

    // C code - direct system calls
    #include <windows.h>
    
    void direct_system_access() {
        // Direct Windows API call
        HANDLE file = CreateFile(
            L"test.txt",
            GENERIC_WRITE,
            0,
            NULL,
            CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL,
            NULL
        );
        
        if (file != INVALID_HANDLE_VALUE) {
            // Use the file handle
            CloseHandle(file); // Manual cleanup
        }
    }
    
  3. No Automatic Exception Handling

    // C code - manual error handling
    int divide_numbers(int a, int b) {
        if (b == 0) {
            // Manual error handling - no exceptions
            return -1; // Error code
        }
        return a / b;
    }
    

Interop Between Managed and Unmanaged Code:

  1. P/Invoke (Platform Invoke)

    public class PInvokeExample
    {
        // Import unmanaged Windows API
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr GetCurrentProcess();
        
        [DllImport("user32.dll")]
        public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
        
        public void CallUnmanagedCode()
        {
            // Call unmanaged function from managed code
            IntPtr process = GetCurrentProcess();
            MessageBox(IntPtr.Zero, "Hello from unmanaged code!", "Message", 0);
        }
    }
    
  2. COM Interop

    public class COMInteropExample
    {
        public void UseCOMObject()
        {
            // Create COM object from managed code
            var excel = new Microsoft.Office.Interop.Excel.Application();
            excel.Visible = true;
            
            // Use COM object
            var workbook = excel.Workbooks.Add();
            var worksheet = workbook.ActiveSheet;
            worksheet.Cells[1, 1] = "Hello from COM!";
            
            // Cleanup (important for COM objects)
            System.Runtime.InteropServices.Marshal.ReleaseComObject(worksheet);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(excel);
        }
    }
    
  3. C++/CLI (Managed C++)

    // C++/CLI code - can mix managed and unmanaged
    #include <iostream>
    #include <msclr/marshal_cppstd.h>
    
    using namespace System;
    using namespace msclr::interop;
    
    public ref class MixedCode
    {
    public:
        void ManagedMethod()
        {
            // Managed code
            String^ managedString = "Hello from managed C++";
            Console::WriteLine(managedString);
        }
        
        void UnmanagedMethod()
        {
            // Unmanaged code
            std::cout << "Hello from unmanaged C++" << std::endl;
        }
    };
    

Comparison Summary:

AspectManaged CodeUnmanaged Code
Memory ManagementAutomatic (GC)Manual (malloc/free)
Type SafetyEnforced by CLRNo enforcement
Exception HandlingStructuredManual error codes
SecurityCode Access SecurityNo built-in security
PerformanceSlight overheadMaximum performance
PlatformCross-platformPlatform-specific
DevelopmentFaster, saferMore control, more risk

Related Resources

Value Types:

  • Stored directly on the stack (or inline in containing types)
  • Contain the actual data directly
  • Each variable has its own copy of the data
  • When you copy a value type, you copy the actual data
  • Examples: int, double, bool, struct, enum, decimal, DateTime
  • Derive from System.ValueType
  • Cannot be null (unless nullable value type like int?)

Reference Types:

  • Stored on the heap
  • Variable contains a reference (pointer) to the actual data
  • Multiple variables can reference the same object
  • When you copy a reference type, you copy the reference, not the data
  • Examples: class, interface, delegate, string, object, arrays
  • Derive from System.Object
  • Can be null

Key Differences Illustrated:

Value Type Example:

int a = 10;
int b = a;  // Creates a COPY of the value
b = 20;
Console.WriteLine(a);  // Output: 10 (unchanged)
Console.WriteLine(b);  // Output: 20

Reference Type Example:

class Person { public string Name { get; set; } }

Person person1 = new Person { Name = "John" };
Person person2 = person1;  // Copies the REFERENCE, not the object
person2.Name = "Jane";

Console.WriteLine(person1.Name);  // Output: "Jane" (changed!)
Console.WriteLine(person2.Name);  // Output: "Jane"
// Both variables point to the same object in memory

Memory Allocation:

  • Value types are typically faster to allocate/deallocate (stack allocation)
  • Reference types require heap allocation and garbage collection
  • Value types have less memory overhead (no object header)

Performance Implications:

  • Use value types for small, simple data structures
  • Use reference types when you need shared references or large data
  • Boxing occurs when converting value type to reference type (performance cost)

String:

  • Immutable - once created, cannot be changed
  • Any modification creates a new string object in memory
  • Thread-safe by nature (due to immutability)
  • Use when: Few modifications, constant strings, or passing data between methods

Example of immutability:

string str = "Hello";
str = str + " World";  // Creates a NEW string object, original "Hello" is garbage collected

StringBuilder:

  • Mutable - can be modified without creating new objects
  • More efficient for multiple string manipulations
  • Not thread-safe (use synchronization if needed)
  • Use when: Multiple modifications, loops, or building large strings

Performance comparison:

// BAD PRACTICE - Creates 1000 new string objects
string result = "";
for (int i = 0; i < 1000; i++)
{
    result += i.ToString();  // Very inefficient!
}

// GOOD PRACTICE - Modifies same StringBuilder object
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++)
{
    sb.Append(i.ToString());  // Efficient!
}
string result = sb.ToString();

When to use each:

  • String: 1-5 concatenations, constant values, simple operations
  • StringBuilder: Loops, multiple operations, building complex strings, performance-critical code

Related Resources