C# Advanced Concepts


Master advanced C# features that every professional developer should know.

Delegates

Definition: Type-safe function pointers that reference methods with a specific signature.

Basic Delegate
// Define delegate type
public delegate void NotifyDelegate(string message);

// Method matching delegate signature
public void SendEmail(string message)
{
    Console.WriteLine($"Email: {message}");
}

// Usage
NotifyDelegate notify = SendEmail;
notify("Hello World");  // Calls SendEmail
Multicast Delegates
NotifyDelegate notify = SendEmail;
notify += SendSMS;
notify += LogToFile;

notify("Alert!");  // Calls all three methods
Built-in Delegates
// Action<T> - no return value
Action<string> print = (msg) => Console.WriteLine(msg);
print("Hello");

// Func<T, TResult> - has return value
Func<int, int, int> add = (a, b) => a + b;
int sum = add(5, 3);

// Predicate<T> - returns bool
Predicate<int> isEven = (n) => n % 2 == 0;
bool result = isEven(4); // true

Lambda Expressions

Definition: Anonymous functions that provide a concise syntax for writing inline methods.

Lambda Syntax
// Expression lambda
Func<int, int> square = x => x * x;

// Statement lambda
Func<int, int, int> max = (a, b) =>
{
    if (a > b) return a;
    return b;
};

// Multiple parameters
Func<int, int, int> multiply = (x, y) => x * y;
Lambda with LINQ
var numbers = new List<int> { 1, 2, 3, 4, 5, 6 };

// Filter even numbers
var evens = numbers.Where(n => n % 2 == 0);

// Transform
var squared = numbers.Select(n => n * n);

// Aggregate
var sum = numbers.Aggregate((a, b) => a + b);

LINQ (Language Integrated Query)

Definition: Provides query capabilities directly in C# for collections, databases, and XML.

Query Syntax
var students = new List<Student>
{
    new Student { Name = "Alice", Grade = 85 },
    new Student { Name = "Bob", Grade = 92 },
    new Student { Name = "Charlie", Grade = 78 }
};

// Query syntax
var topStudents = from s in students
                  where s.Grade >= 80
                  orderby s.Grade descending
                  select s.Name;
Method Syntax
// Method syntax (same query)
var topStudents = students
    .Where(s => s.Grade >= 80)
    .OrderByDescending(s => s.Grade)
    .Select(s => s.Name);
Common LINQ Operations
// Filtering
var adults = people.Where(p => p.Age >= 18);

// Projection
var names = people.Select(p => p.Name);

// Ordering
var sorted = people.OrderBy(p => p.Age);

// Grouping
var grouped = people.GroupBy(p => p.City);

// Aggregation
var average = numbers.Average();
var max = numbers.Max();
var count = people.Count(p => p.Age > 30);

// First/Single
var first = people.First();
var single = people.Single(p => p.Id == 1);

Entity Framework (EF)

Definition: Object-Relational Mapping (ORM) framework for .NET that enables developers to work with databases using .NET objects.

Code-First Approach
// Define entity
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// DbContext
public class AppDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    
    protected override void OnConfiguring(DbContextOptionsBuilder options)
    {
        options.UseSqlServer("connection_string");
    }
}
CRUD Operations
using (var context = new AppDbContext())
{
    // Create
    var product = new Product { Name = "Laptop", Price = 999.99m };
    context.Products.Add(product);
    context.SaveChanges();
    
    // Read
    var products = context.Products.Where(p => p.Price > 500).ToList();
    
    // Update
    var item = context.Products.Find(1);
    item.Price = 899.99m;
    context.SaveChanges();
    
    // Delete
    context.Products.Remove(item);
    context.SaveChanges();
}
Relationships
public class Order
{
    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    
    // Navigation property
    public List<OrderItem> Items { get; set; }
}

public class OrderItem
{
    public int Id { get; set; }
    public int OrderId { get; set; }
    public int ProductId { get; set; }
    
    // Navigation properties
    public Order Order { get; set; }
    public Product Product { get; set; }
}

Async/Await

Definition: Asynchronous programming pattern that allows non-blocking operations.

Async Method
public async Task<string> DownloadDataAsync(string url)
{
    using (var client = new HttpClient())
    {
        string result = await client.GetStringAsync(url);
        return result;
    }
}

// Usage
string data = await DownloadDataAsync("https://api.example.com");
Console.WriteLine(data);

Extension Methods

Definition: Add new methods to existing types without modifying them.

public static class StringExtensions
{
    public static bool IsValidEmail(this string email)
    {
        return email.Contains("@") && email.Contains(".");
    }
    
    public static string Reverse(this string str)
    {
        return new string(str.Reverse().ToArray());
    }
}

// Usage
string email = "test@example.com";
bool valid = email.IsValidEmail();  // true

string text = "Hello";
string reversed = text.Reverse();  // "olleH"

C# Operators Explained

Understanding modern C# operators is crucial for writing clean, expressive code. Here are the most important operators explained clearly for intermediate developers.

1. Lambda Operator (=>)

The => operator separates parameters from the expression or statement block in lambda expressions.

// Lambda expression: parameter => expression
Func<int, int> square = x => x * x;
Console.WriteLine(square(5));  // Output: 25

// Multiple parameters
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(3, 4));  // Output: 7

// Statement lambda (with curly braces)
Action<string> greet = name =>
{
    var message = $"Hello, {name}!";
    Console.WriteLine(message);
};

// Used in LINQ
var evens = numbers.Where(n => n % 2 == 0);
2. Null-Coalescing Operator (??)

Returns the left operand if it's not null; otherwise, returns the right operand.

// Basic usage
string name = null;
string displayName = name ?? "Guest";
Console.WriteLine(displayName);  // Output: Guest

// Chaining
string result = first ?? second ?? third ?? "Default";

// Common use case
var timeout = config.Timeout ?? 30;
3. Null-Coalescing Assignment (??=)

Assigns the right operand to the left operand only if the left operand is null.

// Before: verbose null check
if (cache == null)
    cache = new Dictionary<string, object>();

// After: concise with ??=
cache ??= new Dictionary<string, object>();

// Lazy initialization
private Database _db;
public Database GetDatabase()
{
    _db ??= new Database();  // Initialize only once
    return _db;
}
4. Null-Conditional Operator (?. and ?[])

Safely access members and elements, returning null if the object is null instead of throwing NullReferenceException.

// Member access (?.)
Person person = null;
string name = person?.Name;  // Returns null, doesn't throw

// Chaining
var length = person?.Name?.Length;

// Array/indexer access (?[])
int[] numbers = null;
int? first = numbers?[0];  // Returns null

// Real-world example
var city = customer?.Address?.City ?? "Unknown";

// Safe event invocation
OnDataChanged?.Invoke(this, EventArgs.Empty);
5. Range Operator (..)

Creates a range of indices for slicing arrays and collections.

int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// Get elements from index 2 to 5 (exclusive)
var slice1 = numbers[2..5];  // { 2, 3, 4 }

// From start to index 3
var slice2 = numbers[..3];   // { 0, 1, 2 }

// From index 7 to end
var slice3 = numbers[7..];   // { 7, 8, 9 }

// Last 3 elements
var slice4 = numbers[^3..];  // { 7, 8, 9 }

// String slicing
string text = "Hello World";
string hello = text[..5];    // "Hello"
6. Index from End Operator (^)

Specifies an index from the end of a sequence.

int[] numbers = { 1, 2, 3, 4, 5 };

var last = numbers[^1];      // 5 (last element)
var secondLast = numbers[^2]; // 4

// With strings
string text = "Programming";
char lastChar = text[^1];     // 'g'

// Combined with range
var lastThree = numbers[^3..]; // { 3, 4, 5 }
7. Namespace Alias Qualifier (::)

Accesses members of an aliased namespace, useful when there are naming conflicts.

// Define alias at top of file
using MyCompany = Company.MyApplication.Core;
using ThirdParty = ExternalLibrary.Core;

// Both have a class named "Logger"
MyCompany::Logger logger1 = new MyCompany::Logger();
ThirdParty::Logger logger2 = new ThirdParty::Logger();

// Global namespace alias
global::System.Console.WriteLine("Using global System");
8. Switch Expression (=>)

The => operator is also used in switch expressions for pattern matching.

// Modern switch expression
string GetDayType(DayOfWeek day) => day switch
{
    DayOfWeek.Saturday or DayOfWeek.Sunday => "Weekend",
    _ => "Weekday"
};

// With pattern matching
decimal CalculateDiscount(Customer customer) => customer switch
{
    { IsPremium: true, YearsActive: > 5 } => 0.20m,
    { IsPremium: true } => 0.15m,
    { YearsActive: > 3 } => 0.10m,
    _ => 0.05m
};
9. Discard Operator (_)

Indicates that a value is intentionally unused.

// Ignore out parameters
if (int.TryParse("123", out _))
    Console.WriteLine("Valid number");

// Deconstruction - ignore some values
var (name, _, age) = GetPersonInfo();

// Pattern matching - default case
var result = value switch
{
    1 => "One",
    2 => "Two",
    _ => "Other"
};
10. Null-Forgiving Operator (!)

Tells the compiler that a nullable expression is not null (use with caution!).

string? nullableString = GetString();

// Without !, compiler warns
int length1 = nullableString.Length;  // Warning!

// With !, suppress the warning
int length2 = nullableString!.Length;  // No warning

// After null check
if (name != null)
    ProcessName(name!);  // Tell compiler it's safe

// WARNING: Only use when CERTAIN it's not null!
Operator Quick Reference
Operator Name Purpose Example
=> Lambda Define lambda expressions x => x * 2
?? Null-coalescing Provide default for null name ?? "Guest"
??= Null-coalescing assignment Assign if null list ??= new()
?. Null-conditional Safe member access obj?.Property
?[] Null-conditional index Safe indexer access arr?[0]
.. Range Create index range arr[2..5]
^ Index from end Index from end of sequence arr[^1]
:: Namespace alias Access aliased namespace MyAlias::Class
_ Discard Ignore unused values var (x, _) = tuple
! Null-forgiving Suppress null warning obj!.Method()

Summary

Concept Purpose Use Case
Delegates Function pointers Callbacks, events
Lambda Anonymous functions LINQ, inline logic
LINQ Query collections Data filtering, transformation
EF ORM framework Database operations
Async/Await Asynchronous programming Non-blocking I/O