andersch.dev

<2022-05-04 Wed>

C#

C# is a a managed, garbage-collected, object-oriented programming language that is part of the .NET ecosystem. It is heavily inspired by Java.

.NET

.NET describes the Ecosystem that includes languages (C#, F#, VB, …), runtimes like the CLR (Common Language Runtime) and libraries like the BCL (Base Class Library).

Implementations of .NET include:

  • .NET Framework: Runs on Windows
  • .NET Core: New and open source
  • Mono: Clean-room implementation of .NET, originally for Linux

The future vision of it is to have only a single .NET SDK with one base class library and a unified toolchain.

Terminology:

  • Nuget: C# package manager
  • CLR: the core Common Language Runtime, analogous to the JVM in Java
  • ASP.NET: Active Server Pages .NET. A web application framework.

dotnet cli: your entry point, SDK, driver, javac, etc

  • dotnet new: templates
  • dotnet run: dev time compile and run
  • dotnet publish: ready up for deployment

.csproj: XML file that contains the list of files in a C# project, references to system assemblies and other settings. You get one .csproj file per assembly and the .sln (solution file) ties together all the assemblies that make up the project.

Concepts

Properties

Expose class members (fields) with controlled access, without having to explicitly write getter/setter methods. They look like fields but act like methods.

using System;

public class Person
{
    /* Auto-implemented property (compiler generates backing field) */
    public string Name { get; set; }

    /* Property with custom logic (requires writing the backing field) */
    private int _age;
    public int Age
    {
        get { return _age; }
        set {
            if (value < 0) throw new ArgumentOutOfRangeException("Age cannot be negative.");
            _age = value;
        }
    }
}

/* Usage: */
class _ { static void Main(string[] args) {
    Person p = new Person();
    p.Name = "Alice";     // Calls set
    string name = p.Name; // Calls get
    Console.WriteLine($"Hello {p.Name}!");
    // p.Age = -1; // would throw exception
}}

Delegates

Delegates are type-safe function pointers that can point to methods (since they know which instance the method belongs to). They are used in a similar way to C's typedef to avoid function pointer syntax.

using System;

/* define delegate type */
public delegate int MathOperation(int a, int b);

public class Calculator
{
    public static int Add(int x, int y) { return x + y; }
    public int Subtract(int x, int y) => x - y; // expression-bodied member syntax
}

/* Usage: */
class _ { static void Main(string[] args) {
    /* using predefined delegate */
    MathOperation op1 = Calculator.Add; // Points to static method
    Calculator calc   = new Calculator();

    /* using built-in generic 'Func' delegate */
    Func<int, int, int> op2 = calc.Subtract; // Points to instance method

    Console.WriteLine("{0}", op2(op1(5, 3),4));
}}

Events

  • Events (or the Observer pattern) are a first-class language feature in C#.
  • Classes can subscribe to an event (using += syntax) with a callback function.
  • Defined using delegates, which define the signature of the callback function.
  • Built-in delegate types EventHandler and EventHandler<T> can be used
using System;
public class Button
{
    public delegate void ClickHandler(object sender, EventArgs e); // Delegate for the event
    public event ClickHandler Clicked; // The event (== null if no subscribes)

    public void SimulateClick()
    {
        Clicked?.Invoke(this, EventArgs.Empty); // fire the event (null-conditional invoke)
    }
}

/* Usage: */
class _ { static void Main(string[] args) {
    Button btn = new Button();
    btn.Clicked += (sender, e) => Console.WriteLine("Button was clicked!"); // Subscribe with a lambda
    btn.SimulateClick();
}}

LINQ (Language Integrated Query)

Provides SQL-like query capabilities directly in C# to query various data sources (collections, databases, XML, etc.) in a type-safe way.

using System;
using System.Linq;
using System.Collections.Generic;
class _ { static void Main(string[] args) {
    List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    // Query Syntax
    var evenNumbersQuery = from num in numbers
                           where num % 2 == 0
                           select num * 2;

    // Method Syntax (using extension methods and lambdas)
    var oddNumbersMethod = numbers.Where(num => num % 2 != 0)
                                  .OrderByDescending(num => num)
                                  .Select(num => $"Odd: {num}");

    Console.WriteLine("Even Numbers (doubled):");
    foreach (var num in evenNumbersQuery) {
        Console.WriteLine(num);
    }
}}

Async/Await

Used for asynchronous programming.

public async Task<string> DownloadDataAsync(string url)
{
    using (HttpClient client = new HttpClient())
    {
        // await pauses execution here until GetStringAsync completes,
        // but doesn't block the calling thread.
        string data = await client.GetStringAsync(url);
        return data; // This runs after the await completes
    }
}
/* Usage: */
// string content = await DownloadDataAsync("http://example.com");

Extension Methods

Add methods to existing types without modifying their source code. They are static methods in a static class, where the first parameter (marked with this) specifies the type being extended.

public static class StringExtensions
{
    public static int WordCount(this string str) // 'this' marks it as an extension method
    {
        return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
    }
}
// Usage:
// string s = "Hello C# world!";
// int count = s.WordCount(); // Called as if it's an instance method of string

Structs vs. Classes

struct (Value type)

  • Stored on the stack or inline within an object
  • Copied on assignment.
  • System.ValueType is their base.
  • Primitives (int, double, bool, char, etc.) are structs.

class (Reference type)

  • Variable holds a reference (pointer) to an object on the heap.
  • Assignment copies the reference, not the object.
  • System.Object is their base.

using Statement

Similar to C++'s RAII and Python's with, C# can ensure cleanup of resources at the end of a scope by calling their Dispose() method (meaning they must implement IDisposable).

using (StreamReader reader = new StreamReader("file.txt"))
{
    // Use reader
} // reader.Dispose() is called here

Nullable Value Types T?

Allows value types (int, bool, struct) to also represent a null state.

using System;
class _ { static void Main(string[] args) {

    int?  age        = null; // A nullable integer
    bool? isEmployed = true;

    if (age.HasValue) {
        Console.WriteLine($"Age: {age.Value}");
    } else {
        Console.WriteLine("Age is not specified.");
    }

    /* Null-coalescing operator */
    int currentAge = age ?? 25; // If age is null, use 25 (else use age)

    Console.WriteLine($"{currentAge}");

}}

Attributes

Declarative tags used to add metadata to your code (assemblies, types, methods, properties, etc.). Can be queried at runtime using reflection.

[Serializable] // An attribute
[Obsolete("Use NewMethod instead.")]
public class MyClass
{
    [Conditional("DEBUG")] // Method only compiled/called in DEBUG builds
    public void DebugLog(string message) { /* ... */ }
}

dynamic Keyword

Bypasses compile-time type checking. Operations on dynamic types are resolved at runtime. Useful for interoperating with dynamic languages (like Python, Ruby) or COM.

dynamic obj = GetSomeDynamicObject();
string name = obj.Name; // Property access resolved at runtime
obj.DoSomething(10);    // Method call resolved at runtime

record (C# 9+)

A reference type (or value type with record struct) primarily designed for encapsulating data. Compiler automatically generates constructors, properties, Equals, GetHashCode, ToString, and deconstruction methods based on the properties. Often immutable by default.

public record Person(string FirstName, string LastName); // Concise immutable record
// Equivalent to a class with properties, constructor, Equals, GetHashCode, etc.

// Usage:
// var person1 = new Person("John", "Doe");
// var person2 = new Person("John", "Doe");
// Console.WriteLine(person1 == person2); // True (value-based equality)

// With-expressions for non-destructive mutation
// var person3 = person1 with { LastName = "Smith" };