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)
- Auto-property initializers
- Expression-bodied members
- String interpolation ($)
- Null-conditional operator (?.)
- Nameof expressions
- Using static
- Exception filters
C# 7.0/7.1/7.2/7.3 (2017-2018)
- Out variables inline
- Tuples and deconstruction
- Pattern matching (is, switch)
- Local functions
- Ref returns and locals
- Discards (_)
- Throw expressions
- Default literal
C# 8.0 (2019)
- Nullable reference types
- Default interface methods
- Switch expressions
- Indices and ranges (^, ..)
- Async streams (IAsyncEnumerable)
- Using declarations
C# 9.0 (2020)
- Records (record class, record struct)
- Init-only properties
- Top-level programs
- Pattern matching enhancements (and, or, not)
- Target-typed new expressions
- Covariant returns
- Static lambdas
C# 10.0 (2021)
- File-scoped namespaces
- Global using directives
- Constant interpolated strings
- Record struct improvements
- Lambda natural types
- Caller argument expressions
C# 11.0 (2022)
- Raw string literals (""" """)
- Generic math (static abstract interfaces)
- Required members
- List patterns
- UTF-8 string literals
- Auto-default structs
C# 12.0 (2023)
- Primary constructors for classes
- Collection expressions ([1, 2, 3])
- Default lambda parameters
- Alias any type
- 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:
- Value types vs Reference types (memory layout)
- Boxing and unboxing performance
- Stack vs Heap allocation
- Garbage collection and finalization
- IDisposable and using patterns
- Span and Memory
- Unsafe code and pointers