MODULE 3: MODERN C# FEATURES - Complete Guide

36. C# 6.0 Features (2015) - The Productivity Release

using System;
using System.Collections.Generic;
using static System.Math; // Using static members (C# 6)
using System.IO;

public class CSharp6Features
{
// 1. AUTO-PROPERTY INITIALIZERS
public string Name { get; set; } = "Default Name";
public List<string> Items { get; set; } = new List<string>();
public Guid Id { get; } = Guid.NewGuid(); // Getter-only with initializer
// 2. READ-ONLY PROPERTIES (Getter-only)
public DateTime CreatedDate { get; } = DateTime.Now;
public string ComputedValue => $"Value: {DateTime.Now}"; // Expression-bodied
// 3. EXPRESSION-BODIED MEMBERS (=>)
// Constructor
public CSharp6Features(string name) => Name = name;
// Destructor
~CSharp6Features() => Console.WriteLine("Destroyed");
// Method
public string GetFormattedName() => $"Mr/Ms {Name}";
// Property (already shown)
public string Display => $"ID: {Id}, Name: {Name}";
// Indexer
public string this[int index] => Items[index];
// 4. STRING INTERPOLATION ($)
public void StringInterpolation()
{
string name = "John";
int age = 30;
// Old way
string oldWay = string.Format("Name: {0}, Age: {1}", name, age);
// New way (C# 6)
string newWay = $"Name: {name}, Age: {age}";
// With expressions
string withCalc = $"Next year you'll be {age + 1}";
// With formatting
string price = $"Price: {19.99:C}"; // $19.99
string date = $"Today: {DateTime.Now:yyyy-MM-dd}";
// With method calls
string upper = $"Uppercase: {name.ToUpper()}";
// Complex expressions
string conditional = $"Status: {(age >= 18 ? "Adult" : "Minor")}";
Console.WriteLine(newWay);
}
// 5. NULL-CONDITIONAL OPERATOR (?.)
public void NullConditionalOperator()
{
List<string> list = null;
// Old way
int? oldCount = null;
if (list != null)
oldCount = list.Count;
// New way
int? newCount = list?.Count; // Returns null if list is null
// With null coalescing
int count = list?.Count ?? 0;
// Chaining
string name = list?.Count.ToString()?.ToUpper();
// For events (thread-safe invocation)
EventHandler handler = null;
handler?.Invoke(this, EventArgs.Empty);
// Indexer with null-conditional
string firstItem = list?[0];
}
// 6. NULL COALESCING ASSIGNMENT (??=) - Actually C# 8 but included here
public void NullCoalescingAssignment()
{
List<string> list = null;
// Old way
if (list == null)
list = new List<string>();
// New way (C# 8)
list ??= new List<string>();
// Works with any nullable
int? number = null;
number ??= 10; // number becomes 10
}
// 7. NAME OF EXPRESSION
public void NameOfExpression(string parameterName)
{
// Gets the name as string at compile time
string paramName = nameof(parameterName); // "parameterName"
string methodName = nameof(GetFormattedName); // "GetFormattedName"
string propertyName = nameof(Name); // "Name"
string className = nameof(CSharp6Features); // "CSharp6Features"
// Perfect for argument validation
if (string.IsNullOrEmpty(parameterName))
throw new ArgumentException("Parameter cannot be empty", nameof(parameterName));
// For property changed notifications
OnPropertyChanged(nameof(Name));
}
private void OnPropertyChanged(string propertyName) { }
// 8. USING STATIC (Import static members)
public void UsingStaticDemo()
{
// Using static System.Math at top of file
double result = Sqrt(Pow(2, 3) + PI); // Instead of Math.Sqrt, Math.Pow, Math.PI
// With Console (if using static System.Console)
// WriteLine("Hello"); // Would work with using static System.Console
}
// 9. EXCEPTION FILTERS (Enhanced)
public void ExceptionFilters()
{
try
{
ProcessData();
}
catch (Exception ex) when (ex.Message.Contains("timeout"))
{
// Only catch timeout errors
Console.WriteLine("Timeout occurred");
}
catch (Exception ex) when (LogException(ex))
{
// Filter that always returns false (logs but doesn't catch)
}
}
private bool LogException(Exception ex)
{
Console.WriteLine($"Logged: {ex.Message}");
return false; // Don't catch
}
private void ProcessData() { }
// 10. INDEX INITIALIZERS (For dictionaries)
public void IndexInitializers()
{
// Old way
var dict1 = new Dictionary<string, int>
{
{ "one", 1 },
{ "two", 2 }
};
// New way (C# 6)
var dict2 = new Dictionary<string, int>
{
["one"] = 1,
["two"] = 2,
["three"] = 3
};
// Works with any indexer
var matrix = new int[3, 3]
{
[0, 0] = 1,
[1, 1] = 2,
[2, 2] = 3
};
}
// 11. AWAIT IN CATCH/FINALLY (C# 6)
public async Task AwaitInCatchAsync()
{
try
{
await DoWorkAsync();
}
catch (Exception ex)
{
await LogErrorAsync(ex); // Wasn't allowed before C# 6
}
finally
{
await CleanupAsync(); // Also allowed in finally
}
}
private Task DoWorkAsync() => Task.CompletedTask;
private Task LogErrorAsync(Exception ex) => Task.CompletedTask;
private Task CleanupAsync() => Task.CompletedTask;
}

// Real-world C# 6 example
public class CustomerViewModel : INotifyPropertyChanged
{
// Auto-property initializers
public List<Order> Orders { get; set; } = new List<Order>();
// Read-only auto-property
public Guid CustomerId { get; } = Guid.NewGuid();
// Expression-bodied property
public string DisplayName => $"{FirstName} {LastName}".Trim();
// Expression-bodied constructor
public CustomerViewModel(string firstName, string lastName) =>
(FirstName, LastName) = (firstName, lastName);
public string FirstName { get; set; }
public string LastName { get; set; }
// String interpolation in action
public string GetGreeting() => $"Welcome, {DisplayName}!";
// NameOf for property changed
public void UpdateName(string firstName)
{
if (firstName != FirstName)
{
FirstName = firstName;
OnPropertyChanged(nameof(FirstName));
OnPropertyChanged(nameof(DisplayName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

public class Order { }
public interface INotifyPropertyChanged { }
public class PropertyChangedEventArgs : EventArgs
{
public PropertyChangedEventArgs(string propertyName) { }
}
public delegate void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e);

37. C# 7.0 Features (2017) - The Functional Boost

using System;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Generic;

public class CSharp7Features
{
// 1. OUT VARIABLES (Inline declaration)
public void OutVariables()
{
// Old way
int oldResult;
if (int.TryParse("123", out oldResult))
{
Console.WriteLine(oldResult);
}
// New way - declare inline
if (int.TryParse("456", out int newResult))
{
Console.WriteLine(newResult);
}
// With var
if (int.TryParse("789", out var resultWithVar))
{
Console.WriteLine(resultWithVar);
}
// Discard (_) when you don't need the value
if (int.TryParse("abc", out _))
{
// Parsing succeeded but we don't need the value
}
}
// 2. TUPLES (Lightweight data structures)
public void Tuples()
{
// Tuple creation
var tuple1 = (1, "Hello"); // Item1, Item2
var tuple2 = (Id: 1, Name: "John"); // Named members
// Return tuple from method
(int x, int y) GetCoordinates() => (10, 20);
// Deconstruction
(int id, string name) = tuple2;
var (x, y) = GetCoordinates();
// Tuple deconstruction with discard
var (_, lastName) = ("John", "Doe");
// Tuple comparison (C# 7.3+)
var point1 = (X: 10, Y: 20);
var point2 = (X: 10, Y: 20);
bool areEqual = point1 == point2; // True
// Tuple in dictionary
var dict = new Dictionary<(int, int), string>();
dict[(1, 2)] = "Point 1";
// Tuple as parameter
void ProcessPoint((int X, int Y) point)
{
Console.WriteLine($"X: {point.X}, Y: {point.Y}");
}
// Nested tuples
var nested = (1, (2, 3));
Console.WriteLine(nested.Item2.Item1);
}
// 2b. VALUE TUPLE vs TUPLE
public void ValueTupleVsTuple()
{
// Tuple (reference type, heap allocated, slower)
Tuple<int, string> oldTuple = new Tuple<int, string>(1, "Hello");
// ValueTuple (value type, stack allocated, faster)
(int, string) newTuple = (1, "Hello");
// ValueTuple with names
(int Id, string Name) namedTuple = (1, "John");
// Performance difference matters in hot paths
}
// 3. PATTERN MATCHING (Basic patterns)
public void PatternMatching()
{
object obj = 42;
// Type pattern (is)
if (obj is int i)
{
Console.WriteLine($"Integer: {i}");
}
// Switch pattern (C# 7)
switch (obj)
{
case int integer:
Console.WriteLine($"Integer: {integer}");
break;
case string s:
Console.WriteLine($"String: {s}");
break;
case null:
Console.WriteLine("Null");
break;
default:
Console.WriteLine("Unknown");
break;
}
// When guard in switch
switch (obj)
{
case int n when n > 0:
Console.WriteLine($"Positive: {n}");
break;
case int n when n < 0:
Console.WriteLine($"Negative: {n}");
break;
case int n:
Console.WriteLine($"Zero");
break;
}
}
// 4. LOCAL FUNCTIONS
public int Fibonacci(int n)
{
// Local function defined inside method
int Fib(int x)
{
if (x <= 1) return x;
return Fib(x - 1) + Fib(x - 2);
}
return Fib(n);
}
public async Task<string> ProcessWithLocalFunction()
{
// Local function with async
async Task<string> LocalProcess()
{
await Task.Delay(100);
return "Result";
}
// Local function with recursion
int Factorial(int x) => x <= 1 ? 1 : x * Factorial(x - 1);
// Local function with generics
T Max<T>(T a, T b) where T : IComparable<T> => a.CompareTo(b) > 0 ? a : b;
var result = await LocalProcess();
var fact = Factorial(5);
var max = Max(10, 20);
return result;
}
// 5. REF RETURNS AND REF LOCALS
public void RefReturnsAndLocals()
{
int[] numbers = { 1, 2, 3, 4, 5 };
// Method that returns reference to array element
ref int Find(int target)
{
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == target)
return ref numbers[i];
}
throw new Exception("Not found");
}
// Store reference locally
ref int refToThree = ref Find(3);
refToThree = 100; // Changes numbers[2] to 100
Console.WriteLine(string.Join(", ", numbers)); // 1, 2, 100, 4, 5
}
// 6. DISCARD (_) VARIABLES
public void Discards()
{
// Ignoring out parameters
if (int.TryParse("123", out _))
{
Console.WriteLine("Parsed successfully");
}
// Ignoring tuple elements
var (_, lastName, _) = ("John", "Doe", 30);
Console.WriteLine(lastName); // "Doe"
// Ignoring in deconstruction
var point = (X: 10, Y: 20);
(_, var y) = point;
// Pattern matching discard
object obj = "Hello";
switch (obj)
{
case int i:
Console.WriteLine($"Int: {i}");
break;
case string _:
Console.WriteLine("It's a string");
break;
case _:
Console.WriteLine("Unknown");
break;
}
// Discard with out parameters
void GetValues(out int a, out int b) => (a, b) = (1, 2);
GetValues(out _, out var value2);
}
// 7. EXPRESSION-BODIED MEMBERS (Expanded)
// Now allowed on:
// - Constructors
// - Finalizers
// - Property getters/setters
// - Indexers
private string _name;
public string Name
{
get => _name;
set => _name = value ?? throw new ArgumentNullException(nameof(value));
}
public string this[int index] => index.ToString();
// 8. THROW EXPRESSIONS
public string GetValue(string input)
{
// Throw can now be used in expressions
return input ?? throw new ArgumentNullException(nameof(input));
}
public int ParseNumber(string number)
{
return int.TryParse(number, out var result)
? result
: throw new FormatException($"Cannot parse '{number}'");
}
// 9. PRIVATE PROTECTED (Access modifier)
// private protected = accessible in derived classes within same assembly
private protected string InternalOnlyForDerived { get; set; }
// 10. DEFAULT LITERAL EXPRESSIONS
public void DefaultLiterals()
{
// Old way
int oldDefault = default(int);
string oldStringDefault = default(string);
// New way (compiler infers type)
int newDefault = default;
string newStringDefault = default;
// In method calls
void Process(int value = default) { } // default = default(int)
// With generics
T GetDefault<T>() => default;
}
// 11. ASYNC MAIN METHOD (C# 7.1)
// Entry point can now be async
// static async Task Main(string[] args) is allowed
// static async Task<int> Main() is also allowed
}

// Real-world C# 7 example - Tuple returns and pattern matching
public class OrderProcessor
{
public (bool Success, string ErrorMessage, int OrderId) ProcessOrder(Order order)
{
if (order == null)
return (false, "Order cannot be null", 0);
if (order.Items.Count == 0)
return (false, "Order has no items", 0);
// Process order...
int orderId = 123;
return (true, null, orderId);
}
public void UseProcessor()
{
var order = new Order();
var result = ProcessOrder(order);
if (result.Success)
{
Console.WriteLine($"Order processed: {result.OrderId}");
}
else
{
Console.WriteLine($"Error: {result.ErrorMessage}");
}
// Deconstruct
var (success, error, id) = ProcessOrder(order);
// Deconstruct with discards
var (succeeded, _, orderId) = ProcessOrder(order);
}
// Pattern matching in business logic
public decimal CalculateDiscount(object customer)
{
switch (customer)
{
case PremiumCustomer premium when premium.YearsAsMember > 5:
return 0.25m;
case PremiumCustomer _:
return 0.15m;
case RegularCustomer _:
return 0.05m;
case null:
return 0;
default:
return 0;
}
}
}

public class Order
{
public List<object> Items { get; set; } = new List<object>();
}
public class PremiumCustomer
{
public int YearsAsMember { get; set; }
}
public class RegularCustomer { }

38. C# 8.0 Features (2019) - Null Safety & Patterns

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

#nullable enable // Enable nullable reference types (C# 8)

public class CSharp8Features
{
// 1. NULLABLE REFERENCE TYPES (MAJOR FEATURE)
#nullable enable
public class NullableReferenceDemo
{
// Non-nullable reference type (warning if set to null)
public string NonNullable { get; set; } = "Default";
// Nullable reference type (explicitly can be null)
public string? Nullable { get; set; }
// Constructor must initialize non-nullable
public NullableReferenceDemo(string name)
{
NonNullable = name; // Must be set
Nullable = null; // Can be null
}
public void Process()
{
// Warning: Possible null reference
int length1 = Nullable.Length;
// Safe: Check for null
int length2 = Nullable?.Length ?? 0;
// Null-forgiving operator (!) - tell compiler it's not null
int length3 = Nullable!.Length; // No warning (but risky)
}
}
#nullable restore
// 2. DEFAULT INTERFACE METHODS
public interface ILogger
{
void Log(string message);
// Default implementation (doesn't break existing implementations)
void LogError(string error)
{
Log($"ERROR: {error}");
}
// Static members in interfaces (C# 8)
static ILogger CreateDefault() => new ConsoleLogger();
// Private methods in interfaces
private void InternalHelper() { }
}
public class ConsoleLogger : ILogger
{
public void Log(string message) => Console.WriteLine(message);
// LogError has default implementation, can override if needed
}
// 3. SWITCH EXPRESSIONS (Enhanced pattern matching)
public string GetDayType(DayOfWeek day) => day switch
{
DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend",
DayOfWeek.Monday => "Start of week",
DayOfWeek.Friday => "TGIF",
_ => "Weekday" // Discard pattern
};
// Switch expression with multiple patterns
public decimal CalculateDiscount(Customer customer) => customer switch
{
{ IsPremium: true, YearsAsMember: > 5 } => 0.25m,
{ IsPremium: true } => 0.15m,
{ TotalPurchases: > 1000 } => 0.10m,
{ TotalPurchases: > 500 } => 0.05m,
_ => 0m
};
// Property patterns
public string GetPersonDescription(Person person) => person switch
{
{ Age: >= 18, Name: string name } => $"{name} is an adult",
{ Age: < 18, Name: string name } => $"{name} is a minor",
null => "No person",
_ => "Unknown"
};
// Tuple patterns
public string GetQuadrant(int x, int y) => (x, y) switch
{
( > 0, > 0) => "First",
( < 0, > 0) => "Second",
( < 0, < 0) => "Third",
( > 0, < 0) => "Fourth",
(0, 0) => "Origin",
(_, 0) => "X-axis",
(0, _) => "Y-axis",
_ => "Unknown"
};
// Positional patterns
public string GetPointInfo(Point point) => point switch
{
(0, 0) => "Origin",
(var x, 0) => $"On X-axis at {x}",
(0, var y) => $"On Y-axis at {y}",
(var x, var y) => $"Point at ({x}, {y})"
};
// 4. INDICES AND RANGES (^, .. operators)
public void IndicesAndRanges()
{
string[] names = { "Alice", "Bob", "Charlie", "David", "Eve" };
// Index from end (^)
string last = names[^1]; // "Eve"
string secondLast = names[^2]; // "David"
// Range (..)
string[] firstTwo = names[0..2]; // ["Alice", "Bob"]
string[] lastTwo = names[^2..^0]; // ["David", "Eve"]
string[] all = names[..]; // All elements
string[] fromIndex = names[2..]; // From index 2 to end
string[] toIndex = names[..3]; // From start to index 3
// Range as variable
Range range = 1..3;
string[] middle = names[range]; // ["Bob", "Charlie"]
// Indices with list (performance - O(1) for arrays, O(n) for lists)
List<string> list = new List<string>(names);
string lastItem = list[^1]; // Works but slower than array
// Span support (performance)
Span<int> numbers = stackalloc int[] { 1, 2, 3, 4, 5 };
Span<int> slice = numbers[1..3]; // [2, 3]
}
// 5. ASYNC STREAMS (IAsyncEnumerable<T>)
public async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(100); // Simulate async work
yield return i; // Yield asynchronously
}
}
public async Task ConsumeAsyncStream()
{
await foreach (var number in GenerateNumbersAsync(10))
{
Console.WriteLine(number);
}
// With cancellation
using var cts = new CancellationTokenSource();
await foreach (var number in GenerateNumbersAsync(10).WithCancellation(cts.Token))
{
Console.WriteLine(number);
}
}
// 6. USING DECLARATIONS (Simplified dispose)
public void UsingDeclarations()
{
// Old way (using block)
using (var file1 = new FileStream("file1.txt", FileMode.Open))
{
// Use file1
} // Disposed here
// New way (using declaration) - C# 8
using var file2 = new FileStream("file2.txt", FileMode.Open);
// Use file2
// Disposed at end of scope
} // file2 disposed here
// 7. STATIC LOCAL FUNCTIONS
public void StaticLocalFunctions()
{
int localVar = 10;
// Non-static (can capture localVar)
int AddWithCapture(int x) => x + localVar;
// Static (cannot capture local variables)
static int AddStatic(int x, int y) => x + y;
// Static is more performant (no closure allocation)
int result1 = AddWithCapture(5); // 15
int result2 = AddStatic(5, 3); // 8
}
// 8. NULLABLE REFERENCE TYPES SETTINGS
// Can be enabled at file level: #nullable enable
// Or project level: <Nullable>enable</Nullable> in .csproj
}

// Supporting classes
public class Customer
{
public bool IsPremium { get; set; }
public int YearsAsMember { get; set; }
public decimal TotalPurchases { get; set; }
}

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

public record Point(int X, int Y); // Record (C# 9, but works here)

// Real-world C# 8 example - Data pipeline
public class DataPipeline
{
public async IAsyncEnumerable<string> ReadLinesAsync(string filePath)
{
using var reader = new StreamReader(filePath);
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
yield return line;
}
}
public async Task ProcessFileAsync(string filePath)
{
var lines = ReadLinesAsync(filePath);
await foreach (var line in lines.Take(100)) // Take first 100 lines
{
var parts = line.Split(',');
if (parts.Length >= 3)
{
// Process line
}
}
}
// Nullable reference types in practice
public string? FindUserEmail(int userId)
{
// Returns null if not found
return null;
}
public void ProcessUser(int userId)
{
string? email = FindUserEmail(userId);
if (email != null)
{
// email is treated as non-null here
Console.WriteLine($"Email length: {email.Length}");
}
// Or use null-forgiving operator if sure
// Console.WriteLine(email!.Length);
}
}

39. C# 9.0 Features (2020) - The Records Release

using System;
using System.Collections.Generic;
using System.Linq;

public class CSharp9Features
{
// 1. RECORDS (Immutable data types)
// Basic record declaration
public record Person(string FirstName, string LastName, int Age);
// Record with methods
public record Employee(string Name, int Id)
{
public string GetFormattedName() => $"EMP-{Id}: {Name}";
// Records support inheritance
}
// Record with property attributes
public record Product
{
public string Name { get; init; } // Init-only property
public decimal Price { get; init; }
public string Category { get; init; }
// Constructor
public Product(string name, decimal price, string category) =>
(Name, Price, Category) = (name, price, category);
// Method
public virtual decimal GetTax() => Price * 0.1m;
}
// Record inheritance
public record Book : Product
{
public string Author { get; init; }
public Book(string name, decimal price, string category, string author)
: base(name, price, category) => Author = author;
public override decimal GetTax() => Price * 0.05m; // Books have lower tax
}
// Record struct (C# 10, but preview in 9)
public record struct Point(int X, int Y);
public void RecordDemo()
{
// Create records
var person1 = new Person("John", "Doe", 30);
var person2 = new Person("John", "Doe", 30);
// Value equality (not reference equality)
bool areEqual = person1 == person2; // True! (different from class)
// With-expression (create modified copy)
var person3 = person1 with { Age = 31 };
// Deconstruction
var (firstName, lastName, age) = person1;
// ToString override
Console.WriteLine(person1); // "Person { FirstName = John, LastName = Doe, Age = 30 }"
// Records in collections
var people = new List<Person>();
people.Add(person1);
people.Add(person1 with { FirstName = "Jane" });
}
// 2. INIT-ONLY PROPERTIES
public class WeatherData
{
public DateTime Date { get; init; }
public double Temperature { get; init; }
public double Humidity { get; init; }
// Can set in constructor
public WeatherData(DateTime date) => Date = date;
}
public void InitOnlyDemo()
{
var weather = new WeatherData(DateTime.Now)
{
Temperature = 25.5, // Can set with object initializer
Humidity = 65.0
};
// weather.Temperature = 26.0; // ERROR: Can't change after init
}
// 3. TOP-LEVEL PROGRAMS (No ceremony)
// Instead of:
/*
using System;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World");
}
}
}
*/
// Now you can just write:
// Console.WriteLine("Hello World");
// using statements go at top
// Async Main is supported
// Example top-level program (in Program.cs):
/*
using System;
using System.Threading.Tasks;
Console.WriteLine("Hello World");
string name = args.Length > 0 ? args[0] : "World";
Console.WriteLine($"Hello {name}");
await Task.Delay(1000);
return 0;
*/
// 4. PATTERN MATCHING ENHANCEMENTS
public void PatternMatchingEnhancements()
{
object obj = 42;
// Simple type pattern
if (obj is int i)
{
Console.WriteLine($"Int: {i}");
}
// Relational patterns
string GetTemperatureCategory(double temp) => temp switch
{
< 0 => "Freezing",
>= 0 and < 10 => "Cold",
>= 10 and < 20 => "Cool",
>= 20 and < 30 => "Warm",
>= 30 => "Hot"
};
// Logical patterns (and, or, not)
bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z';
bool IsNotDigit(char c) => c is not >= '0' and <= '9';
// Parenthesized patterns
bool IsValid(int value) => value is (>= 0 and <= 100) or (>= 200 and <= 300);
// Combined patterns
string GetPointCategory(Point p) => p switch
{
(0, 0) => "Origin",
( > 0, > 0) and (var x, var y) when x == y => "Diagonal quadrant I",
( < 0, < 0) and (var x, var y) when x == y => "Diagonal quadrant III",
( > 0, > 0) => "Quadrant I",
( < 0, > 0) => "Quadrant II",
( < 0, < 0) => "Quadrant III",
( > 0, < 0) => "Quadrant IV",
_ => "On axis"
};
// Not pattern
if (obj is not null)
{
Console.WriteLine("Not null");
}
}
// 5. TARGET-TYPED NEW EXPRESSIONS
public void TargetTypedNew()
{
// Old way
Person person1 = new Person("John", "Doe", 30);
Dictionary<string, List<int>> dict1 = new Dictionary<string, List<int>>();
// New way - omit type when it can be inferred
Person person2 = new("Jane", "Doe", 25);
Dictionary<string, List<int>> dict2 = new();
// With arguments
List<int> numbers = new() { 1, 2, 3, 4, 5 };
// As method parameter
void ProcessPerson(Person p) { }
ProcessPerson(new("Bob", "Smith", 40));
}
// 6. COVARIANT RETURNS (Override can return more specific type)
public class Animal { }
public class Dog : Animal { }
public class AnimalFactory
{
public virtual Animal CreateAnimal() => new Animal();
}
public class DogFactory : AnimalFactory
{
public override Dog CreateAnimal() => new Dog(); // Covariant return
}
// 7. STATIC LAMBDA EXPRESSIONS (Prevents capturing)
public void StaticLambdas()
{
int captured = 10;
// Non-static (captures)
Func<int, int> capturedLambda = x => x + captured;
// Static (cannot capture, more performant)
Func<int, int, int> staticLambda = static (x, y) => x + y;
// static lambda with attributes (C# 12 preview)
// Func<int, int> attributed = [SomeAttribute] static x => x * 2;
}
// 8. EXTENSION GETENUMERATOR (foreach over any type)
public class MyCollection<T>
{
private T[] _items = new T[10];
public IEnumerator<T> GetEnumerator()
{
foreach (var item in _items)
yield return item;
}
}
// 9. MODULE INITIALIZERS
// [ModuleInitializer]
// public static void Initialize() { } // Runs when module loads
// 10. NATIVE SIZED INTEGERS (nint, nuint)
public void NativeInts()
{
nint nativeInt = 42; // Platform dependent (32 or 64 bit)
nuint nativeUInt = 100; // Unsigned version
// Useful for interop with native code
// nint matches IntPtr automatically
}
// 11. FUNCTION POINTERS (Unsafe)
public unsafe void FunctionPointers()
{
// delegate*<int, int, int> add = &Add;
// int result = add(5, 3);
}
private static int Add(int a, int b) => a + b;
}

// Real-world C# 9 example - Domain modeling with records
public record Money(decimal Amount, string Currency)
{
public Money Add(Money other)
{
if (Currency != other.Currency)
throw new InvalidOperationException($"Currency mismatch: {Currency} vs {other.Currency}");
return this with { Amount = Amount + other.Amount };
}
public override string ToString() => $"{Amount:C} {Currency}";
}

public record OrderItem(string ProductId, int Quantity, Money UnitPrice)
{
public Money Total => UnitPrice with { Amount = UnitPrice.Amount * Quantity };
}

public record Order(string OrderId, DateTime OrderDate, List<OrderItem> Items)
{
public Money Total => Items.Aggregate(
new Money(0, Items.FirstOrDefault()?.UnitPrice.Currency ?? "USD"),
(sum, item) => sum.Add(item.Total)
);
public bool IsValid => Items.Any() && Items.All(i => i.Quantity > 0);
}

// Usage
public class OrderService
{
public void ProcessOrder()
{
var order = new Order(
OrderId: "ORD-123",
OrderDate: DateTime.Now,
Items: new()
{
new("PROD-1", 2, new Money(19.99m, "USD")),
new("PROD-2", 1, new Money(49.99m, "USD"))
}
);
if (order.IsValid)
{
Console.WriteLine($"Order total: {order.Total}");
}
}
}

40. C# 10.0 Features (2021) - The Cleanup Release

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

// GLOBAL USING (C# 10)
global using System.Text; // Available in all files
global using System.Threading.Tasks;
global using static System.Math; // Global using static

// FILE-SCOPED NAMESPACES
namespace CSharp10Features; // No braces! Saves one indentation level

public class CSharp10Features
{
// 1. FILE-SCOPED NAMESPACES (shown above)
// Instead of:
// namespace Name { class Class { } }
// Now: namespace Name; class Class { }
// 2. GLOBAL USING DIRECTIVES
// In a file (e.g., GlobalUsings.cs):
// global using System;
// global using System.Collections.Generic;
// global using System.Linq;
// Available everywhere without explicit using
// 3. CONSTANT INTERPOLATED STRINGS
public const string ApplicationName = "MyApp";
public const string Version = "1.0";
public const string FullName = $"{ApplicationName} v{Version}"; // Now allowed!
public void ConstantInterpolation()
{
Console.WriteLine(FullName); // "MyApp v1.0"
}
// 4. IMPROVED DEFERRED STRUCTS (record struct)
public record struct Person(string Name, int Age);
public void RecordStructDemo()
{
// Value type record (struct)
var person1 = new Person("John", 30);
var person2 = person1 with { Age = 31 };
// Person is a struct, so copied by value
Console.WriteLine(person1.Age); // 30 (unchanged)
Console.WriteLine(person2.Age); // 31
}
// 5. LAMBDA IMPROVEMENTS
public void LambdaImprovements()
{
// Natural type for lambdas
var add = (int a, int b) => a + b; // Now has natural type Func<int,int,int>
// Lambdas can have attributes (C# 12 preview)
// var log = [Description("Logs message")] (string msg) => Console.WriteLine(msg);
// Lambda return type specification
var parse = int? (string s) => int.TryParse(s, out var i) ? i : null;
// Lambda with explicit return type
var createList = static List<T> <T>(params T[] items) => items.ToList();
}
// 6. CALLER ARGUMENT EXPRESSIONS
public void CallerArgumentExpression(
bool condition,
[CallerArgumentExpression("condition")] string message = null)
{
if (!condition)
{
throw new ArgumentException($"Assertion failed: {message}");
}
}
public void TestCallerArgument()
{
int x = 5, y = 10;
CallerArgumentExpression(x < y); // message = "x < y"
// CallerArgumentExpression(x > y); // Throws with message "x > y"
}
// 7. EXTENDED PROPERTY PATTERNS
public void ExtendedPropertyPatterns()
{
var person = new Person { Name = "John", Address = new Address { City = "New York" } };
// Old way
if (person.Address != null && person.Address.City == "New York") { }
// New way (nested property pattern)
if (person is { Address.City: "New York" })
{
Console.WriteLine("Person lives in New York");
}
// With when guard
if (person is { Age: > 18 } adult)
{
Console.WriteLine($"Adult: {adult.Name}");
}
}
// 8. INTERPOLATED STRING HANDLERS (Performance)
// Custom interpolated string handling for high-performance logging
public class Logger
{
public void Log(LogLevel level, [InterpolatedStringHandlerArgument("")] ref LogInterpolatedStringHandler handler)
{
if (handler.Enabled)
{
// Custom handling
}
}
}
[InterpolatedStringHandler]
public ref struct LogInterpolatedStringHandler
{
public bool Enabled { get; }
public LogInterpolatedStringHandler(int literalLength, int formattedCount, Logger logger, LogLevel level)
{
Enabled = level >= LogLevel.Information;
}
public void AppendLiteral(string s) { }
public void AppendFormatted<T>(T t) { }
}
public enum LogLevel { Debug, Information, Warning, Error }
// 9. STRUCT WITH PARAMETERLESS CONSTRUCTORS (C# 10)
public struct PointStruct
{
public int X { get; set; }
public int Y { get; set; }
// Parameterless constructor (allowed in C# 10)
public PointStruct()
{
X = 10;
Y = 20;
}
// Field initializers in structs (C# 10)
private string _name = "Default";
public PointStruct(int x, int y) : this()
{
X = x;
Y = y;
}
}
// 10. EXTENDED NAME OF SCOPE
public void ExtendedNameOf()
{
// nameof can now refer to static members
string className = nameof(CSharp10Features);
string methodName = nameof(ExtendedNameOf);
string propertyName = nameof(ApplicationName); // Static property
}
// 11. ASYNC METHOD BUILDER
// Custom async method builder for performance
// [AsyncMethodBuilder(typeof(MyTaskMethodBuilder<>))]
// public class MyTask<T> { }
// 12. ALLOW REF IN ASYNC METHODS (with restrictions)
// ref struct can't be used in async, but improvements in C# 10
public async Task UseSpanInAsync()
{
// Span<int> span = stackalloc int[10]; // Still not allowed in async
// But improvements for ValueTask and cancellation
await Task.Delay(100);
}
}

// Supporting classes
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Address Address { get; set; }
}

public class Address
{
public string City { get; set; }
}

// Real-world C# 10 example - Minimal API (ASP.NET Core)
/*
// Program.cs
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World");
app.MapGet("/users/{id}", (int id) => new User(id, "John Doe"));

app.Run();

public record User(int Id, string Name);
*/

// Use file-scoped namespace and global using
namespace CSharp10Examples;

// Global usings make code cleaner
global using System.Text.Json;

public class DataService
{
public async Task<string> GetDataAsync()
{
var data = new { Message = "Hello", Timestamp = DateTime.Now };
return JsonSerializer.Serialize(data);
}
}

41. C# 11.0 Features (2022) - The Generic Math Release

using System;
using System.Numerics;
using System.Collections.Generic;

namespace CSharp11Features;

public class CSharp11Features
{
// 1. RAW STRING LITERALS (""" ... """)
public void RawStringLiterals()
{
// Multi-line string without escape sequences
string json = """
{
"name": "John Doe",
"age": 30,
"address": {
"street": "123 Main St",
"city": "New York"
}
}
""";
// With indentation (whitespace is trimmed based on closing """)
string sql = """
SELECT *
FROM Users
WHERE Age > 18
""";
// Including quotes and braces naturally
string html = """
<div class="container">
<h1>Hello World</h1>
<p>This is a "quoted" text</p>
</div>
""";
// With interpolation
string name = "John";
string message = $"""
Hello {name}!
Welcome to C# 11.
""";
// Raw string with multiple quotes (use more quotes)
string withQuotes = """"
This string can contain """ three quotes
Without any escaping!
"""";
}
// 2. GENERIC MATH (Static abstract interfaces)
// Allows mathematical operations on generics
public T Add<T>(T a, T b) where T : INumber<T>
{
return a + b; // Works for any numeric type!
}
public T Sum<T>(params T[] numbers) where T : INumber<T>
{
T sum = T.Zero;
foreach (var num in numbers)
sum += num;
return sum;
}
// Custom type supporting generic math
public struct Vector2<T> where T : INumber<T>
{
public T X { get; set; }
public T Y { get; set; }
public static Vector2<T> operator +(Vector2<T> a, Vector2<T> b) =>
new() { X = a.X + b.X, Y = a.Y + b.Y };
public static Vector2<T> operator -(Vector2<T> a, Vector2<T> b) =>
new() { X = a.X - b.X, Y = a.Y - b.Y };
}
public void GenericMathDemo()
{
int intSum = Sum(1, 2, 3, 4, 5); // 15
double doubleSum = Sum(1.5, 2.5, 3.5); // 7.5
decimal decSum = Sum(10.5m, 20.3m); // 30.8m
// Vector with different numeric types
var vec1 = new Vector2<double> { X = 1.0, Y = 2.0 };
var vec2 = new Vector2<double> { X = 3.0, Y = 4.0 };
var vec3 = vec1 + vec2; // Works!
}
// 3. REQUIRED MEMBERS
public class Product
{
// Must be set during object initialization
public required string Name { get; init; }
public required decimal Price { get; init; }
public string? Description { get; set; } // Optional
}
public void RequiredMembersDemo()
{
// Must set all required members
var product = new Product
{
Name = "Laptop",
Price = 999.99m
};
// Error: Missing required members
// var invalid = new Product { Name = "Mouse" };
}
// 4. AUTO-DEFAULT STRUCT (Structs initialize fields automatically)
public struct UserData
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
// No need for constructor - fields auto-initialized to default values
}
// 5. LIST PATTERNS (Pattern matching for arrays/lists)
public void ListPatterns()
{
int[] numbers = { 1, 2, 3, 4, 5 };
// Matching array length and elements
string GetDescription(int[] arr) => arr switch
{
[] => "Empty array",
[1] => "Single element 1",
[1, 2] => "Exactly [1, 2]",
[1, _, 3] => "Starts with 1, ends with 3",
[1, .., 5] => "Starts with 1, ends with 5",
[.., 5] => "Ends with 5",
[1, .. var middle, 5] => $"First 1, last 5, middle has {middle.Length} elements",
_ => "Other pattern"
};
Console.WriteLine(GetDescription(numbers)); // "Starts with 1, ends with 5"
// Slice pattern with discard
if (numbers is [1, .., 5])
{
Console.WriteLine("Matches pattern");
}
// List patterns with nested patterns
int[][] matrix = { new[] { 1, 2 }, new[] { 3, 4 } };
string GetMatrixInfo(int[][] m) => m switch
{
[[1, 2], [3, 4]] => "2x2 identity-like matrix",
[[var a, var b], _] => $"First row: {a}, {b}",
_ => "Unknown"
};
}
// 6. SPAN PATTERN MATCHING
public void SpanPatternMatching()
{
Span<char> span = "Hello World".ToCharArray();
if (span is ['H', 'e', 'l', 'l', 'o', ..])
{
Console.WriteLine("Starts with Hello");
}
}
// 7. STATIC ABSTRACT/VIRTUAL INTERFACE MEMBERS (Part of Generic Math)
public interface IGetRandom<T> where T : IGetRandom<T>
{
static abstract T GetRandom();
}
public class RandomInt : IGetRandom<RandomInt>
{
public int Value { get; set; }
public static RandomInt GetRandom()
{
var random = new Random();
return new RandomInt { Value = random.Next() };
}
}
// 8. UTF-8 STRING LITERALS
public void Utf8Literals()
{
// Old way
byte[] oldBytes = Encoding.UTF8.GetBytes("Hello");
// New way (C# 11)
ReadOnlySpan<byte> utf8Bytes = "Hello"u8;
// Use in APIs that expect UTF-8
// httpClient.PostAsync(url, new ByteArrayContent("Hello"u8.ToArray()));
}
// 9. NEGATIVE INDEX IN PATTERN MATCHING (C# 11 refinement)
public void NegativeIndexPatterns()
{
int[] numbers = { 1, 2, 3, 4, 5 };
string GetInfo(int[] arr) => arr switch
{
[^1] => "Single element",
[.., var last] => $"Last element: {last}",
_ => "Unknown"
};
Console.WriteLine(GetInfo(numbers)); // "Last element: 5"
}
// 10. IMPROVED METHOD GROUP CONVERSION TO DELEGATE
public void MethodGroupImprovements()
{
// Old way (C# 1-10)
Func<int, int, int> addOld = new Func<int, int, int>(Add);
// C# 11 improved natural conversion
Func<int, int, int> add = Add; // Simpler and more efficient
// Works with complex overloads
Action<string> log = Console.WriteLine;
}
private int Add(int a, int b) => a + b;
// 11. NEWLINE IN INTERPOLATED STRINGS
public void NewlineInterpolation()
{
string name = "John";
int age = 30;
string message = $"""
Name: {name}
Age: {age}
Status: {(age >= 18 ? "Adult" : "Minor")}
""";
}
// 12. UNSAFE CODE IMPROVEMENTS
public unsafe void UnsafeImprovements()
{
// Ref fields in ref structs (improved)
Span<int> numbers = stackalloc int[] { 1, 2, 3, 4, 5 };
ref int first = ref numbers[0];
// Pattern matching on spans
if (numbers is [1, 2, ..])
{
Console.WriteLine("Starts with 1,2");
}
}
}

// Generic Math - Real-world example
public class StatisticsCalculator<T> where T : INumber<T>
{
public T CalculateAverage(T[] numbers)
{
T sum = T.Zero;
foreach (var num in numbers)
sum += num;
return sum / T.CreateChecked(numbers.Length); // Division works for all numeric types
}
public (T Min, T Max) FindMinMax(T[] numbers)
{
if (numbers.Length == 0)
throw new ArgumentException("Array cannot be empty");
T min = numbers[0];
T max = numbers[0];
foreach (var num in numbers)
{
if (num < min) min = num;
if (num > max) max = num;
}
return (min, max);
}
}

// Required members with class hierarchy
public class Vehicle
{
public required string Make { get; init; }
public required string Model { get; init; }
}

public class Car : Vehicle
{
public required int NumberOfDoors { get; init; }
public bool IsElectric { get; init; }
}

// Usage
public class RealWorldExample
{
public void Demo()
{
// Required members ensure proper initialization
var car = new Car
{
Make = "Tesla",
Model = "Model 3",
NumberOfDoors = 4,
IsElectric = true
};
// Generic math calculator
var calc = new StatisticsCalculator<double>();
double[] temps = { 23.5, 24.0, 22.5, 25.0, 23.0 };
var (min, max) = calc.FindMinMax(temps);
double avg = calc.CalculateAverage(temps);
Console.WriteLine($"Min: {min}, Max: {max}, Avg: {avg:F1}");
// Raw string literals for SQL queries
int userId = 123;
string sql = $"""
SELECT Id, Name, Email
FROM Users
WHERE Id = {userId}
AND IsActive = 1
ORDER BY Name
""";
}
}

42. C# 12.0 Features (2023) - The Latest Release

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace CSharp12Features;

// 1. PRIMARY CONSTRUCTORS (Now for classes and structs)
public class ProductService(string connectionString, ILogger logger)
{
// Fields can be initialized from primary constructor parameters
private readonly string _connectionString = connectionString;
private readonly ILogger _logger = logger;
// Can use parameters directly in methods
public void Process() => _logger.Log($"Using connection: {_connectionString}");
// Primary constructor parameters are available throughout the class
public string GetConnectionInfo() => $"Connecting to {connectionString}";
}

// Struct with primary constructor
public struct Point(double x, double y)
{
public double X { get; set; } = x;
public double Y { get; set; } = y;
public double Distance => Math.Sqrt(X * X + Y * Y);
}

// Record with primary constructor (enhanced)
public record Customer(string Name, string Email)
{
// Additional members
public string FullInfo => $"{Name} <{Email}>";
}

// 2. COLLECTION EXPRESSIONS ([...])
public void CollectionExpressions()
{
// New unified syntax for all collection types
// Arrays
int[] numbers1 = new int[] { 1, 2, 3 }; // Old
int[] numbers2 = { 1, 2, 3 }; // Old (implicit)
int[] numbers3 = [1, 2, 3]; // New! C# 12
// Lists
List<string> names1 = new List<string> { "Alice", "Bob" };
List<string> names2 = ["Alice", "Bob"]; // New!
// Spans
Span<int> span = [1, 2, 3, 4, 5];
ReadOnlySpan<char> chars = ['a', 'b', 'c'];
// Collection expression with spread operator (..)
int[] first = [1, 2, 3];
int[] second = [4, 5, 6];
int[] combined = [..first, ..second, 7, 8, 9]; // [1,2,3,4,5,6,7,8,9]
// Empty collections
int[] emptyArray = [];
List<string> emptyList = [];
Span<int> emptySpan = [];
// In method calls
ProcessNumbers([1, 2, 3, 4, 5]);
ProcessNames(["John", "Jane", "Bob"]);
// With dictionary (C# 12)
Dictionary<string, int> scores = [["Alice", 95], ["Bob", 87]];
// Note: Dictionary collection expressions are in preview
}

private void ProcessNumbers(int[] numbers) { }
private void ProcessNames(List<string> names) { }

// 3. DEFAULT LAMBDA PARAMETERS
public void DefaultLambdaParameters()
{
// Lambda with default parameter values (C# 12)
var greet = (string name, string greeting = "Hello") => $"{greeting}, {name}!";
Console.WriteLine(greet("John")); // "Hello, John!"
Console.WriteLine(greet("Jane", "Hi")); // "Hi, Jane!"
// With type inference
var increment = (int x, int step = 1) => x + step;
Console.WriteLine(increment(5)); // 6
Console.WriteLine(increment(5, 3)); // 8
// Complex default expressions
var log = (string message, DateTime timestamp = default) =>
$"{timestamp:yyyy-MM-dd HH:mm:ss}: {message}";
var createList = (params int[] numbers, bool sorted = false) =>
sorted ? numbers.OrderBy(x => x).ToList() : numbers.ToList();
}

// 4. ALIAS ANY TYPE (Using alias directive)
// Using alias for any type, including tuples, arrays, etc.
global using Point3D = (int X, int Y, int Z);
global using StringList = System.Collections.Generic.List<string>;
global using IntArray = int[];
global using NameValue = (string Name, int Value);

public class TypeAliasDemo
{
public void UseAliases()
{
// Using tuple alias
Point3D point = (10, 20, 30);
Console.WriteLine($"Point: {point.X}, {point.Y}, {point.Z}");
// Using list alias
StringList names = ["Alice", "Bob", "Charlie"];
// Using array alias
IntArray numbers = [1, 2, 3, 4, 5];
// Using tuple alias with method
NameValue GetData() => ("John", 30);
var (name, value) = GetData();
}
}

// 5. INLINE ARRAYS (For high-performance scenarios)
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
}

public class InlineArrayDemo
{
public void UseInlineArray()
{
var buffer = new Buffer10<int>();
// Use like a regular array
for (int i = 0; i < 10; i++)
{
buffer[i] = i * i;
}
foreach (var item in buffer)
{
Console.WriteLine(item);
}
}
}

// 6. REF READONLY PARAMETERS (Enhanced)
public class RefReadonlyDemo
{
public void ProcessLargeStruct(in LargeStruct data)
{
// in parameter is read-only reference (C# 7.2)
// C# 12 improves with more scenarios
}
public ref readonly int GetReference(int[] array, int index)
{
// Returns read-only reference
return ref array[index];
}
}

public struct LargeStruct
{
public long Value1, Value2, Value3, Value4, Value5;
}

// 7. INTERCEPTORS (Preview feature for source generators)
// [InterceptsLocation("Program.cs", 10, 5)]
// static void InterceptMethod() { }

// 8. ADDITIONAL PATTERN MATCHING ENHANCEMENTS
public class PatternMatchingCSharp12
{
public void NewPatterns()
{
// Extended property patterns with ref structs
Span<int> numbers = [1, 2, 3, 4, 5];
if (numbers is [var first, .. var rest] && rest.Length > 0)
{
Console.WriteLine($"First: {first}, Rest count: {rest.Length}");
}
// List patterns with more flexibility
int[] arr = [1, 2, 3, 4, 5];
string GetPattern(int[] a) => a switch
{
[1, 2, .., 5] => "Starts with 1,2 and ends with 5",
[1, .. var middle, 5] => $"First 1, last 5, middle: {middle.Length}",
[.., 5] => "Ends with 5",
_ => "Other"
};
}
}

// Real-world C# 12 example - Modern application structure
public class ModernApp
{
// Using primary constructor for DI
public class UserService(string connectionString, ILogger logger) : IUserService
{
private readonly List<User> _users = [];
public async Task<List<User>> GetUsersAsync()
{
logger.Log($"Using DB: {connectionString}");
// Collection expression for return
return [new User(1, "John"), new User(2, "Jane")];
}
public async Task<User?> FindUserAsync(int id)
{
var user = _users.FirstOrDefault(u => u.Id == id);
return user is not null ? user : null;
}
}
// Using collection expressions everywhere
public class DataProcessor
{
private int[] _data = [1, 2, 3, 4, 5];
private List<string> _names = ["Alice", "Bob", "Charlie"];
private Dictionary<string, int> _scores = [["Math", 95], ["Science", 87]];
public int[] ProcessData() => [.. _data.Select(x => x * 2)];
public List<string> GetActiveNames() =>
[.. _names.Where(n => n.Length > 3)];
}
// Default lambda parameters in LINQ
public class QueryProcessor
{
public List<T> GetItems<T>(List<T> items,
Func<T, bool> filter = null,
Func<T, T> transform = null)
{
filter ??= x => true;
transform ??= x => x;
return [.. items.Where(filter).Select(transform)];
}
public void Demo()
{
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// Using default parameters
var all = GetItems(numbers);
var evens = GetItems(numbers, filter: x => x % 2 == 0);
var doubled = GetItems(numbers, transform: x => x * 2);
var doubledEvens = GetItems(numbers,
filter: x => x % 2 == 0,
transform: x => x * 2);
}
}
}

// Supporting interfaces and classes
public interface IUserService
{
Task<List<User>> GetUsersAsync();
Task<User?> FindUserAsync(int id);
}

public interface ILogger
{
void Log(string message);
}

public record User(int Id, string Name);

// Complete modern program with C# 12 features
/*
// Program.cs - Using top-level statements, collection expressions, primary constructors

using System;

var service = new UserService("Server=localhost;Database=MyDb", new ConsoleLogger());
var users = await service.GetUsersAsync();

Console.WriteLine($"Users: {string.Join(", ", users.Select(u => u.Name))}");

public class UserService(string connectionString, ILogger logger) : IUserService
{
public async Task<List<User>> GetUsersAsync()
{
logger.Log($"Connecting to {connectionString}");
await Task.Delay(100);
return [new User(1, "John"), new User(2, "Jane")];
}
}

public record User(int Id, string Name);
public interface IUserService { Task<List<User>> GetUsersAsync(); }
public class ConsoleLogger : ILogger { public void Log(string msg) => Console.WriteLine(msg); }
public interface ILogger { void Log(string message); }

*/

// Summary of C# 12 features:
// 1. Primary constructors for classes and structs
// 2. Collection expressions with [] syntax
// 3. Default lambda parameters
// 4. Alias any type with using directive
// 5. Inline arrays for performance
// 6. Ref readonly enhancements
// 7. Interceptors (preview)
// 8. Pattern matching improvements

Module 3 Summary - What You've Learned

C# 6.0 (2015)

  1. Auto-property initializers
  2. Expression-bodied members
  3. String interpolation ($)
  4. Null-conditional operator (?.)
  5. Nameof expressions
  6. Using static
  7. Exception filters

C# 7.0/7.1/7.2/7.3 (2017-2018)

  1. Out variables inline
  2. Tuples and deconstruction
  3. Pattern matching (is, switch)
  4. Local functions
  5. Ref returns and locals
  6. Discards (_)
  7. Throw expressions
  8. Default literal

C# 8.0 (2019)

  1. Nullable reference types
  2. Default interface methods
  3. Switch expressions
  4. Indices and ranges (^, ..)
  5. Async streams (IAsyncEnumerable)
  6. Using declarations

C# 9.0 (2020)

  1. Records (record class, record struct)
  2. Init-only properties
  3. Top-level programs
  4. Pattern matching enhancements (and, or, not)
  5. Target-typed new expressions
  6. Covariant returns
  7. Static lambdas

C# 10.0 (2021)

  1. File-scoped namespaces
  2. Global using directives
  3. Constant interpolated strings
  4. Record struct improvements
  5. Lambda natural types
  6. Caller argument expressions

C# 11.0 (2022)

  1. Raw string literals (""" """)
  2. Generic math (static abstract interfaces)
  3. Required members
  4. List patterns
  5. UTF-8 string literals
  6. Auto-default structs

C# 12.0 (2023)

  1. Primary constructors for classes
  2. Collection expressions ([1, 2, 3])
  3. Default lambda parameters
  4. Alias any type
  5. Inline arrays

Practice Exercises for Module 3

Exercise 1: Modern Record Usage

// Create a record hierarchy for an e-commerce system
// - Base record: Product with Name, Price
// - Derived record: ElectronicProduct with WarrantyMonths
// - Derived record: BookProduct with Author, ISBN
// Implement With-expression usage
// Show deconstruction

Exercise 2: Collection Expressions Everywhere

// Rewrite this old code using C# 12 features:
var numbers = new List<int>();
numbers.Add(1);
numbers.Add(2);
numbers.Add(3);

var dict = new Dictionary<string, int>();
dict.Add("one", 1);
dict.Add("two", 2);

int[] combined = numbers.ToArray();
// Use collection expressions and spread operator

Exercise 3: Generic Math Implementation

// Implement a Matrix<T> class that works with any numeric type
// Support addition, multiplication
// Use generic math constraints (INumber<T>)
// Demonstrate with int, double, decimal matrices

Exercise 4: Async Streams (C# 8)

// Create a data streaming service
// - IAsyncEnumerable<DataChunk> ReadDataAsync()
// - Simulate paginated API calls
// - Use await foreach and cancellation

Ready for Module 4?

Module 4: .NET Runtime Deep Dive covers:

  1. Value types vs Reference types (memory layout)
  2. Boxing and unboxing performance
  3. Stack vs Heap allocation
  4. Garbage collection and finalization
  5. IDisposable and using patterns
  6. Span and Memory
  7. Unsafe code and pointers