0% found this document useful (0 votes)
7 views77 pages

CSharp Complete Guide

The C# Complete Developer Guide covers intermediate to advanced programming concepts, including refactoring techniques, the use of enums, properties, multidimensional arrays, nested loops, recursion, and dictionaries. It emphasizes best practices for code clarity, maintainability, and performance. The guide also provides examples and patterns for effective coding in C#.

Uploaded by

raularb
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
7 views77 pages

CSharp Complete Guide

The C# Complete Developer Guide covers intermediate to advanced programming concepts, including refactoring techniques, the use of enums, properties, multidimensional arrays, nested loops, recursion, and dictionaries. It emphasizes best practices for code clarity, maintainability, and performance. The guide also provides examples and patterns for effective coding in C#.

Uploaded by

raularb
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

C# Complete Developer Guide — Intermediate to Advanced

Table of Contents

C# Complete Developer Reference Guide

Intermediate to Advanced Concepts

INTERMEDIATE

1. Refactoring
Refactoring means improving the structure of existing code without changing its behavior. It is not
rewriting — it is cleaning.

Why Refactor?
Code rots. What starts clean becomes tangled as features get added under pressure. Refactoring keeps
code readable, testable, and changeable.

Extract Method
When a block of code does one identifiable thing, extract it into its own method.
// BEFORE — hard to skim, long method doing too much
void ProcessOrder(Order order)
{
// Validate
if (order == null) throw new ArgumentNullException(nameof(order));
if ([Link] == 0) throw new InvalidOperationException("Order is empty.");
if ([Link] == null) throw new InvalidOperationException("No customer.");

// Calculate total
decimal total = 0;
foreach (var item in [Link])
total += [Link] * [Link];
if ([Link]) total *= 0.9m;

// Send confirmation
string message = $"Order confirmed. Total: {total:C}";

Page 1 of 77
C# Complete Developer Guide — Intermediate to Advanced

[Link]([Link], "Order Confirmation", message);


}

// AFTER — each method has one job


void ProcessOrder(Order order)
{
ValidateOrder(order);
decimal total = CalculateTotal(order);
SendConfirmation([Link], total);
}

void ValidateOrder(Order order)


{
if (order == null) throw new ArgumentNullException(nameof(order));
if ([Link] == 0) throw new InvalidOperationException("Order is empty.");
if ([Link] == null) throw new InvalidOperationException("No customer.");
}

decimal CalculateTotal(Order order)


{
decimal total = [Link](i => [Link] * [Link]);
if ([Link]) total *= 0.9m;
return total;
}

void SendConfirmation(Customer customer, decimal total)


{
string message = $"Order confirmed. Total: {total:C}";
[Link]([Link], "Order Confirmation", message);
}

Rename for Clarity


Names are the most powerful documentation tool you have.
// BEFORE
int d = 7;
bool f(int x) => x > 18;
List<int> lst = new List<int>();

// AFTER
int daysUntilExpiry = 7;
bool IsAdult(int age) => age > 18;
List<int> eligibleUserIds = new List<int>();

Replace Magic Numbers with Constants


// BEFORE
if (speed > 120) ApplyFine();

Page 2 of 77
C# Complete Developer Guide — Intermediate to Advanced

discount = price * 0.15m;

// AFTER
const int SpeedLimitKph = 120;
const decimal DiscountRate = 0.15m;

if (speed > SpeedLimitKph) ApplyFine();


discount = price * DiscountRate;

Eliminate Dead Code


Remove commented-out code, unused variables, and unreachable branches. Source control tracks
history — you do not need it as a comment.
// BEFORE
// void OldCalculate() { ... } // maybe useful later?
int result = Compute(x);
// int backup = result; // old approach

// AFTER
int result = Compute(x);

Guard Clauses (Early Return)


Replace deeply nested conditionals with early returns to reduce indentation and reveal the happy path.
// BEFORE — pyramid of doom
void ProcessPayment(User user, Order order)
{
if (user != null)
{
if ([Link])
{
if (order != null)
{
if ([Link] > 0)
{
ChargeCard(user, [Link]);
}
}
}
}
}

// AFTER — guard clauses


void ProcessPayment(User user, Order order)
{
if (user == null) return;
if (![Link]) return;
if (order == null) return;

Page 3 of 77
C# Complete Developer Guide — Intermediate to Advanced

if ([Link] <= 0) return;

ChargeCard(user, [Link]);
}

2. Enums
An enum is a named set of integer constants. Use them whenever a variable can only hold one of a fixed
set of meaningful values.

Basic Enum
public enum Direction
{
North, // = 0 (default)
South, // = 1
East, // = 2
West // = 3
}

Direction heading = [Link];

if (heading == [Link])
[Link]("Heading north.");

Assigning Custom Values


public enum HttpStatus
{
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500
}

HttpStatus status = [Link];


[Link]((int)status); // 404

Enum in Switch
public enum Season { Spring, Summer, Autumn, Winter }

string GetClothing(Season season) => season switch


{
[Link] => "Light jacket",

Page 4 of 77
C# Complete Developer Guide — Intermediate to Advanced

[Link] => "T-shirt",


[Link] => "Hoodie",
[Link] => "Heavy coat",
_ => throw new ArgumentOutOfRangeException(nameof(season))
};

Parsing and Converting


// int → enum
int code = 2;
Direction d = (Direction)code; // East

// string → enum (throws if invalid)


Direction parsed = [Link]<Direction>("West");

// Safe parse
if ([Link]<Direction>("West", out Direction result))
[Link](result);

// enum → string
[Link]([Link]()); // "South"

// All values
foreach (Direction dir in [Link]<Direction>())
[Link](dir);

Why Enums Over Strings/Ints


// BAD — strings are typo-prone, not discoverable
void SetState(string state) { }
SetState("runing"); // typo, silent bug

// GOOD — compiler catches errors


void SetState(GameState state) { }
SetState([Link]); // autocompleted, validated

3. Properties
Properties are class members that look like fields but execute code when read or written. They are the
C# way to implement encapsulation.

Auto-Properties
public class Person
{
public string Name { get; set; } // read + write
public int Age { get; private set; } // read anywhere, write only inside class

Page 5 of 77
C# Complete Developer Guide — Intermediate to Advanced

public string Id { get; } = [Link]().ToString(); // read-only, set once


}

Full Properties with Backing Fields


public class Circle
{
private double radius;

public double Radius


{
get => radius;
set
{
if (value < 0) throw new ArgumentException("Radius cannot be negative.");
radius = value;
}
}

// Computed property — no backing field needed


public double Area => [Link] * radius * radius;
public double Circumference => 2 * [Link] * radius;
}

Init-Only Properties (C# 9+)


init means the property can be set only at construction time, making the object immutable after
creation.
public class Point
{
public double X { get; init; }
public double Y { get; init; }
}

var p = new Point { X = 3.0, Y = 4.0 };


// p.X = 5.0; // ERROR — cannot assign after init

Properties vs Fields: When to Use Each


Use public fields almost never — they give callers unrestricted access and cannot be extended later
without breaking changes.
Use properties for anything public or protected. They let you add validation, lazy loading, or
notifications later without changing the caller.

public class Temperature


{
private double celsius;

public double Celsius

Page 6 of 77
C# Complete Developer Guide — Intermediate to Advanced

{
get => celsius;
set => celsius = value;
}

// Derived property — no storage, pure computation


public double Fahrenheit => celsius * 9 / 5 + 32;
public double Kelvin => celsius + 273.15;
}

4. Multidimensional Arrays
C# supports both true multidimensional arrays ([,]) and jagged arrays ([][]). They serve different
purposes.

2D Array (Rectangular)
All rows have the same length. Stored in one contiguous block of memory.
// Declare and initialize
int[,] grid = new int[3, 4]; // 3 rows, 4 columns

// Access
grid[0, 0] = 10;
grid[2, 3] = 99;

// Iterate
for (int row = 0; row < [Link](0); row++)
{
for (int col = 0; col < [Link](1); col++)
{
[Link](grid[row, col] + "\t");
}
[Link]();
}

// Initialize inline
int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
[Link](matrix[1, 2]); // 6

Page 7 of 77
C# Complete Developer Guide — Intermediate to Advanced

3D Array
int[,,] cube = new int[4, 4, 4]; // 4x4x4 cube
cube[0, 0, 0] = 1;
cube[3, 3, 3] = 64;

Jagged Array (Array of Arrays)


Each row can have a different length. More flexible, slightly less cache-friendly.
int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2 };
jagged[1] = new int[] { 3, 4, 5, 6 };
jagged[2] = new int[] { 7 };

// Access
[Link](jagged[1][2]); // 5

// Each row has its own length


for (int i = 0; i < [Link]; i++)
for (int j = 0; j < jagged[i].Length; j++)
[Link](jagged[i][j] + " ");

When to Use Which


Type
Use When
int[,]
Fixed-size grids, matrices, game boards
int[][]
Variable row sizes, triangular data
List<List<T>>
Dynamic size, needs Add/Remove

5. Nested Loops
Nested loops are loops inside loops. Each iteration of the outer loop runs the entire inner loop.

Page 8 of 77
C# Complete Developer Guide — Intermediate to Advanced

Basic Pattern
// Multiplication table
for (int i = 1; i <= 5; i++)
{
for (int j = 1; j <= 5; j++)
{
[Link]($"{i * j,4}");
}
[Link]();
}

Breaking Out of Nested Loops


break only exits the innermost loop. Use a flag or goto to exit multiple levels.
// Using a flag
bool found = false;
for (int i = 0; i < rows && !found; i++)
{
for (int j = 0; j < cols; j++)
{
if (grid[i, j] == target)
{
[Link]($"Found at ({i},{j})");
found = true;
break;
}
}
}

// Using a local function to return early


(int row, int col) FindValue(int[,] grid, int target)
{
for (int i = 0; i < [Link](0); i++)
for (int j = 0; j < [Link](1); j++)
if (grid[i, j] == target)
return (i, j);
return (-1, -1);
}

Performance Awareness
Nested loops multiply complexity. Two loops each running N times = O(N²). Watch for this in game
loops.
// O(N²) — acceptable for small N
for (int i = 0; i < [Link]; i++)
for (int j = i + 1; j < [Link]; j++)
CheckCollision(enemies[i], enemies[j]);

Page 9 of 77
C# Complete Developer Guide — Intermediate to Advanced

// If [Link] = 1000, that's ~500,000 checks per frame!


// Use spatial partitioning (QuadTree, Grid) for large N

6. Recursion
Recursion is when a method calls itself. Every recursive solution needs a base case (the stop condition)
and a recursive case (the step toward the base).

Classic Example: Factorial


int Factorial(int n)
{
if (n <= 1) return 1; // Base case
return n * Factorial(n - 1); // Recursive case
}

[Link](Factorial(5)); // 120
// Stack: Factorial(5) → 5 * Factorial(4) → 5 * 4 * Factorial(3) → ...

Fibonacci
// Naive recursive — O(2^n), very slow for large n
int Fib(int n)
{
if (n <= 1) return n;
return Fib(n - 1) + Fib(n - 2);
}

// Memoized — O(n)
Dictionary<int, long> memo = new();
long FibMemo(int n)
{
if (n <= 1) return n;
if ([Link](n, out long cached)) return cached;
memo[n] = FibMemo(n - 1) + FibMemo(n - 2);
return memo[n];
}

Traversing a File System


Recursion shines on tree-shaped data.
void PrintDirectory(string path, int indent = 0)
{
string pad = new string(' ', indent * 2);
[Link](pad + [Link](path));

foreach (string dir in [Link](path))

Page 10 of 77
C# Complete Developer Guide — Intermediate to Advanced

PrintDirectory(dir, indent + 1); // recurse into subdirectory

foreach (string file in [Link](path))


[Link](pad + " " + [Link](file));
}

Stack Overflow Risk


Each call uses stack space. Deep recursion (thousands of levels) will crash with a
StackOverflowException. Prefer iteration or increase stack size for known-deep cases.
// Tail-recursive style (C# doesn't optimize tail calls, but logic is cleaner)
int FactTail(int n, int accumulator = 1)
{
if (n <= 1) return accumulator;
return FactTail(n - 1, n * accumulator);
}

7. Dictionary
Dictionary<TKey, TValue> stores key-value pairs. Lookup by key is O(1) on average thanks to hashing.

Basic Operations
var scores = new Dictionary<string, int>();

// Add
scores["Alice"] = 95;
scores["Bob"] = 88;
[Link]("Charlie", 72); // throws if key exists

// Read
int alice = scores["Alice"]; // throws if key missing
if ([Link]("Dave", out int dave))
[Link](dave); // safe read

// Update
scores["Alice"] = 100;

// Remove
[Link]("Bob");

// Check existence
bool hasAlice = [Link]("Alice");

// Iterate
foreach (var kvp in scores)
[Link]($"{[Link]}: {[Link]}");

Page 11 of 77
C# Complete Developer Guide — Intermediate to Advanced

Common Patterns
// Count occurrences of words
string[] words = "the cat sat on the mat".Split();
var freq = new Dictionary<string, int>();

foreach (string word in words)


{
if (![Link](word)) freq[word] = 0;
freq[word]++;
}

// Cleaner with TryGetValue


foreach (string word in words)
{
[Link](word, out int count);
freq[word] = count + 1; // count defaults to 0 if not found
}

// Cleanest with GetValueOrDefault


foreach (string word in words)
freq[word] = [Link](word, 0) + 1;

Dictionary as a Cache
private Dictionary<int, PlayerStats> statsCache = new();

PlayerStats GetStats(int playerId)


{
if (![Link](playerId, out var stats))
{
stats = [Link](playerId); // expensive
statsCache[playerId] = stats;
}
return stats;
}

Page 12 of 77
C# Complete Developer Guide — Intermediate to Advanced

Performance
Characteristics
Operation
Average
Worst
Add
O(1)
O(n) — resize
Lookup
O(1)
O(n) — hash collision
Remove
O(1)
O(n)
Iteration
O(n)
O(n)

8. Other Collections
Beyond List<T> and Dictionary<TKey,TValue>, the standard library provides specialized collections for
specific problems.

Stack — Last In, First Out (LIFO)


var history = new Stack<string>();

[Link]("Home");
[Link]("Products");
[Link]("Cart");

[Link]([Link]()); // "Cart" — look without removing


[Link]([Link]()); // "Cart" — remove and return
[Link]([Link]); // 2

Page 13 of 77
C# Complete Developer Guide — Intermediate to Advanced

// Navigation history, undo stacks, expression evaluation

Queue — First In, First Out (FIFO)


var printQueue = new Queue<string>();

[Link]("[Link]");
[Link]("[Link]");
[Link]("[Link]");

string next = [Link](); // "[Link]"


string peek = [Link](); // "[Link]" — not removed

// Task queues, message processing, BFS graph traversal

HashSet — Unique Values, Fast Lookup


var visited = new HashSet<string>();

[Link]("page1");
[Link]("page2");
[Link]("page1"); // ignored — already exists
[Link]([Link]); // 2

bool seen = [Link]("page2"); // O(1)

// Set operations
var setA = new HashSet<int> { 1, 2, 3, 4 };
var setB = new HashSet<int> { 3, 4, 5, 6 };

[Link](setB); // {1,2,3,4,5,6}
[Link](setB); // {3,4}
[Link](setB); // {1,2}

LinkedList — Efficient Middle Insertions


var list = new LinkedList<int>();
[Link](1);
[Link](3);
[Link](4);

LinkedListNode<int> node = [Link](1);


[Link](node, 2); // Insert 2 after 1 — O(1)

// Useful when you frequently insert/remove from the middle


// Less cache-friendly than List<T> for sequential access

Page 14 of 77
C# Complete Developer Guide — Intermediate to Advanced

SortedDictionary<TKey, TValue> — Always Sorted by Key


var sorted = new SortedDictionary<string, int>
{
["banana"] = 3,
["apple"] = 5,
["cherry"] = 1
};

foreach (var kvp in sorted)


[Link]([Link]); // apple, banana, cherry — alphabetically sorted

Choosing the Right Collection


Need
Use
Ordered list, index access
List<T>
Key-value lookup
Dictionary<TKey,TValue>
Unique values
HashSet<T>
LIFO (undo, call stack)
Stack<T>
FIFO (queues, tasks)
Queue<T>
Sorted by key
SortedDictionary<K,V>
Frequent middle inserts

Page 15 of 77
C# Complete Developer Guide — Intermediate to Advanced

LinkedList<T>

9. params
The params keyword lets a method accept a variable number of arguments as an array, without the
caller needing to create an array explicitly.
int Sum(params int[] numbers)
{
return [Link]();
}

// All of these are valid


[Link](Sum(1, 2, 3)); // 6
[Link](Sum(1, 2, 3, 4, 5)); // 15
[Link](Sum()); // 0
[Link](Sum(new int[] { 1, 2 })); // also valid

Rules and Gotchas


// params must be the LAST parameter
void Log(string prefix, params string[] messages) { }
// Log("INFO", "msg1", "msg2", "msg3"); ✓

// Only ONE params parameter per method


// void Bad(params int[] a, params string[] b) { } ERROR

// Calling with no args passes an empty array (not null)


void Print(params string[] items)
{
[Link]([Link]); // 0 if called as Print()
}

Real-World Use
// String formatting
string Format(string template, params object[] args)
=> [Link](template, args);

// [Link] itself uses params internally


// [Link], [Link] — same concept

// Builder pattern
QueryBuilder Where(string field, params object[] values) { ... }

Page 16 of 77
C# Complete Developer Guide — Intermediate to Advanced

10. Optional Parameters


Optional parameters have default values and can be omitted by callers.
void CreateUser(string name, int age = 18, string role = "User", bool isActive = true)
{
[Link]($"{name}, {age}, {role}, {isActive}");
}

CreateUser("Alice"); // Alice, 18, User, True


CreateUser("Bob", 25); // Bob, 25, User, True
CreateUser("Carol", 30, "Admin"); // Carol, 30, Admin, True
CreateUser("Dave", 22, isActive: false); // named arg — skip role

Named Arguments
Named arguments let you pass values out of order, which improves readability.
void DrawRect(int x, int y, int width, int height, string color = "black") { }

// Without names — what does 50 mean?


DrawRect(10, 20, 50, 30, "red");

// With names — clear intent


DrawRect(x: 10, y: 20, width: 50, height: 30, color: "red");

// Mix positional and named (positional must come first)


DrawRect(10, 20, width: 50, height: 30);

Rules
Optional parameters must appear after required ones
Default values must be compile-time constants (no new, no method calls)
When overriding methods, optional parameter defaults in the base class are used by callers
// Valid defaults
void Foo(int x = 0, string s = "hello", bool b = false, MyEnum e = MyEnum.A) { }

// Invalid defaults
// void Bar(List<int> list = new List<int>()) { } ERROR — not a constant
// void Baz(int x = SomeMethod()) { } ERROR — not a constant

11. Value Types vs Reference Types


This is one of the most important distinctions in C#. Understanding it prevents subtle bugs and
performance issues.

Page 17 of 77
C# Complete Developer Guide — Intermediate to Advanced

How Memory Works


Value types store their data directly in the variable. Reference types store a pointer (reference) to data
on the heap.
// Value type — data lives in 'a' itself
int a = 5;
int b = a; // b gets a COPY of 5
b = 10;
[Link](a); // 5 — unchanged

// Reference type — both variables point to the same object


int[] arr1 = { 1, 2, 3 };
int[] arr2 = arr1; // arr2 gets a copy of the REFERENCE (pointer)
arr2[0] = 99;
[Link](arr1[0]); // 99 — same object was modified!

Value Types in C
// All of these are value types:
int, long, short, byte // Integer types
float, double, decimal // Floating point
bool // Boolean
char // Character
struct // Any struct
enum // Any enum
DateTime, TimeSpan // Common structs
(int x, int y) // Tuples of value types

Reference Types in C
// All of these are reference types:
class // Any class
interface // References to interface implementations
string // Special — immutable reference type
array // int[], string[], etc.
delegate // Action, Func, etc.
object // The root type

Null Behavior
Value types cannot be null by default. Reference types can.
int x = null; // COMPILE ERROR
string s = null; // OK — null reference
object o = null; // OK

int? nullable = null; // OK — Nullable<int>


if ([Link])
[Link]([Link]);

Page 18 of 77
C# Complete Developer Guide — Intermediate to Advanced

Passing to Methods
// Value type — method gets its own copy
void Double(int x) { x *= 2; }
int n = 5;
Double(n);
[Link](n); // 5 — unchanged

// Reference type — method operates on the same object


void Clear(List<int> list) { [Link](); }
var nums = new List<int> { 1, 2, 3 };
Clear(nums);
[Link]([Link]); // 0 — same list was cleared

// ref — pass value type by reference


void DoubleRef(ref int x) { x *= 2; }
int m = 5;
DoubleRef(ref m);
[Link](m); // 10

String — Special Case


string is a reference type but behaves like a value type because it is immutable. Every operation creates
a new string.
string a = "hello";
string b = a; // b references same "hello"
b = b + " world"; // creates NEW string "hello world", reassigns b
[Link](a); // "hello" — unchanged

// This is why StringBuilder is faster for many concatenations


var sb = new StringBuilder();
[Link]("hello");
[Link](" ");
[Link]("world");
string result = [Link](); // one allocation at the end

12. Struct
A struct is a value type that groups related data. Perfect for small, lightweight data that is frequently
copied.

Declaring a Struct
public struct Point
{
public float X;
public float Y;

Page 19 of 77
C# Complete Developer Guide — Intermediate to Advanced

public Point(float x, float y)


{
X = x;
Y = y;
}

public float DistanceTo(Point other)


{
float dx = X - other.X;
float dy = Y - other.Y;
return [Link](dx * dx + dy * dy);
}

public override string ToString() => $"({X}, {Y})";


}

Point a = new Point(0, 0);


Point b = new Point(3, 4);
[Link]([Link](b)); // 5

Structs Are Copied


Point p1 = new Point(1, 2);
Point p2 = p1; // full copy
p2.X = 99;
[Link](p1.X); // 1 — p1 not affected

Immutable Struct Pattern


Prefer immutable structs to avoid mutation surprises.
public readonly struct Vector2D
{
public readonly float X;
public readonly float Y;

public Vector2D(float x, float y) { X = x; Y = y; }

// Returns new instance — original unchanged


public Vector2D Add(Vector2D other) => new Vector2D(X + other.X, Y + other.Y);
public Vector2D Scale(float factor) => new Vector2D(X * factor, Y * factor);
public float Magnitude => [Link](X * X + Y * Y);
public Vector2D Normalized => Scale(1f / Magnitude);

public override string ToString() => $"({X:F2}, {Y:F2})";


}

Page 20 of 77
C# Complete Developer Guide — Intermediate to Advanced

Struct vs Class Decision


// Use struct when ALL of these are true:
// 1. Small size (typically < 16 bytes)
// 2. Short-lived or frequently copied
// 3. Logically represents a single value (coordinates, colors)
// 4. Does not need to be null
// 5. Will not be frequently boxed

// Use class when:


// 1. Larger or variable size
// 2. Needs identity (two distinct objects with same data are different)
// 3. Needs inheritance
// 4. May be null
// 5. Has significant shared state

13. Class (Intermediate)


Beyond basic class syntax, intermediate C# includes advanced patterns for class design.

Constructors and Constructor Chaining


public class HttpRequest
{
public string Method { get; }
public string Url { get; }
public Dictionary<string, string> Headers { get; }
public string? Body { get; }

// Most specific constructor


public HttpRequest(string method, string url, Dictionary<string, string> headers, string? body)
{
Method = method;
Url = url;
Headers = headers ?? new Dictionary<string, string>();
Body = body;
}

// Convenience constructors chain with : this(...)


public HttpRequest(string method, string url)
: this(method, url, new Dictionary<string, string>(), null) { }

public HttpRequest(string method, string url, string body)


: this(method, url, new Dictionary<string, string>(), body) { }
}

Page 21 of 77
C# Complete Developer Guide — Intermediate to Advanced

var req1 = new HttpRequest("GET", "[Link]


var req2 = new HttpRequest("POST", "[Link] "{\"name\":\"Alice\"}");

Static Members
Static members belong to the type, not to any instance.
public class Counter
{
private static int totalCount = 0; // shared across ALL instances
private int instanceId;

public Counter()
{
totalCount++;
instanceId = totalCount;
}

public static int TotalCount => totalCount; // static property


public int Id => instanceId;

public static void Reset() => totalCount = 0; // static method


}

var c1 = new Counter(); // totalCount = 1


var c2 = new Counter(); // totalCount = 2
[Link]([Link]); // 2 — accessed on TYPE, not instance

Nested Classes
Classes can be nested inside other classes. Useful for implementation details.
public class LinkedList<T>
{
// Private nested class — implementation detail
private class Node
{
public T Value;
public Node? Next;
public Node(T value) { Value = value; }
}

private Node? head;


private int count;

public void Add(T value)


{
var node = new Node(value); // Uses nested class
[Link] = head;
head = node;

Page 22 of 77
C# Complete Developer Guide — Intermediate to Advanced

count++;
}

public int Count => count;


}

Object Initializer and Builder Pattern


// Object initializer
var person = new Person
{
Name = "Alice",
Age = 30,
Email = "alice@[Link]"
};

// Builder pattern for complex construction


public class QueryBuilder
{
private string tableName = "";
private List<string> conditions = new();
private int? limitValue;

public QueryBuilder From(string table) { tableName = table; return this; }


public QueryBuilder Where(string condition) { [Link](condition); return this; }
public QueryBuilder Limit(int n) { limitValue = n; return this; }

public string Build()


{
string sql = $"SELECT * FROM {tableName}";
if ([Link]())
sql += " WHERE " + [Link](" AND ", conditions);
if ([Link])
sql += $" LIMIT {limitValue}";
return sql;
}
}

string query = new QueryBuilder()


.From("users")
.Where("age > 18")
.Where("active = 1")
.Limit(50)
.Build();
// SELECT * FROM users WHERE age > 18 AND active = 1 LIMIT 50

Page 23 of 77
C# Complete Developer Guide — Intermediate to Advanced

14. Interfaces
An interface defines a contract — a set of members that implementing types must provide. No
implementation, no fields, no constructors.

Defining and Implementing


public interface IAnimal
{
string Name { get; }
void Speak();
string Describe() => $"I am {Name}"; // Default implementation (C# 8+)
}

public class Dog : IAnimal


{
public string Name { get; }

public Dog(string name) => Name = name;

public void Speak() => [Link]($"{Name} says: Woof!");


// Describe() inherited from interface default
}

public class Cat : IAnimal


{
public string Name { get; }

public Cat(string name) => Name = name;

public void Speak() => [Link]($"{Name} says: Meow.");


public string Describe() => $"I am {Name}, a cat."; // Overrides default
}

Interfaces Enable Polymorphism


IAnimal[] animals = { new Dog("Rex"), new Cat("Whiskers"), new Dog("Buddy") };

foreach (IAnimal animal in animals)


{
[Link](); // Calls each type's own implementation
[Link]([Link]());
}

Multiple Interface Implementation


public interface IReadable { string Read(); }
public interface IWritable { void Write(string data); }
public interface ISeekable { void Seek(int position); }

Page 24 of 77
C# Complete Developer Guide — Intermediate to Advanced

// A class can implement many interfaces


public class FileStream : IReadable, IWritable, ISeekable
{
public string Read() { return ""; }
public void Write(string data) { }
public void Seek(int position) { }
}

Interface Segregation
Keep interfaces focused. A class implementing an interface should need ALL its members.
// BAD — too broad
public interface IWorker
{
void Work();
void Eat();
void Sleep();
void ManageTeam();
}

// GOOD — segregated
public interface IWorkable { void Work(); }
public interface IManageable { void ManageTeam(); }

public class Employee : IWorkable { public void Work() { } }


public class Manager : IWorkable, IManageable
{
public void Work() { }
public void ManageTeam() { }
}

Interfaces vs Abstract Classes


Use interface when:
- Defining a capability (IComparable, IDisposable, IEnumerable)
- Multiple inheritance needed
- Unrelated types need same behavior

Use abstract class when:


- Sharing implementation code
- Constructor logic needed
- Strong "is-a" relationship

15. Type Casting: is, as


Type casting converts a reference to another type. C# provides safe and unsafe ways to do this.

Page 25 of 77
C# Complete Developer Guide — Intermediate to Advanced

Direct Cast (Unsafe)


object obj = "hello";
string s = (string)obj; // Works — obj really is a string
[Link]([Link]);

object num = 42;


string fail = (string)num; // THROWS InvalidCastException at runtime!

is — Check and Pattern Match


object shape = new Circle(5);

// Old style — check then cast


if (shape is Circle)
{
Circle c = (Circle)shape; // Two operations
[Link]([Link]);
}

// Modern style — check and declare (pattern matching)


if (shape is Circle circle)
{
[Link]([Link]); // circle is declared and available here
}

// Works with any type check


if (shape is not null)
[Link]("Not null");

as — Safe Cast (Returns Null on Failure)


object obj = "hello";

string? s = obj as string; // Returns "hello"


Circle? c = obj as Circle; // Returns null (not a Circle)

if (s != null)
[Link]([Link]());

// as only works with reference types and nullable value types


// int i = obj as int; ERROR — use (int?) instead

Pattern Matching in Switch


double Area(object shape) => shape switch
{
Circle c => [Link] * [Link] * [Link],
Rectangle r => [Link] * [Link],

Page 26 of 77
C# Complete Developer Guide — Intermediate to Advanced

Triangle t => 0.5 * [Link] * [Link],


null => throw new ArgumentNullException(nameof(shape)),
_ => throw new ArgumentException("Unknown shape")
};

When to Use Each


Approach
Use When
(Type)x
You are certain of the type
x as Type
You want null on failure, not an exception
x is Type t
You want to check AND use in the same step

16. Switch Pattern Matching


C# switch expressions (C# 8+) are far more powerful than traditional switch statements.

Switch Expression
// Traditional switch statement
string msg;
switch (day)
{
case [Link]:
case [Link]:
msg = "Weekend"; break;
default:
msg = "Weekday"; break;
}

// Switch expression — cleaner


string msg = day switch
{
[Link] or [Link] => "Weekend",

Page 27 of 77
C# Complete Developer Guide — Intermediate to Advanced

_ => "Weekday"
};

Type Patterns
string Describe(object obj) => obj switch
{
int n when n < 0 => $"Negative int: {n}",
int n => $"Positive int: {n}",
string s => $"String: '{s}'",
null => "null",
_ => $"Something else: {[Link]().Name}"
};

Property Patterns
public record Order(decimal Total, bool IsVip, string Status);

string GetDiscount(Order order) => order switch


{
{ IsVip: true, Total: > 1000 } => "20% VIP premium discount",
{ IsVip: true } => "10% VIP discount",
{ Total: > 500 } => "5% bulk discount",
{ Status: "Returning" } => "3% loyalty discount",
_ => "No discount"
};

Tuple Patterns
string RPS(string player1, string player2) => (player1, player2) switch
{
("Rock", "Scissors") => "Player 1 wins",
("Paper", "Rock") => "Player 1 wins",
("Scissors", "Paper") => "Player 1 wins",
var (a, b) when a == b => "Draw",
_ => "Player 2 wins"
};

17. Delegates
A delegate is a type-safe reference to a method. It defines the signature a method must have to be
referenced.

Declaring and Using Delegates


// Declare a delegate type
delegate int MathOperation(int a, int b);

Page 28 of 77
C# Complete Developer Guide — Intermediate to Advanced

// Methods that match the signature


int Add(int a, int b) => a + b;
int Subtract(int a, int b) => a - b;
int Multiply(int a, int b) => a * b;

// Use the delegate


MathOperation op = Add;
[Link](op(3, 4)); // 7

op = Multiply;
[Link](op(3, 4)); // 12

Built-in Delegates: Action and Func


Custom delegate types are rarely needed. Use the built-in ones:
// Action<T...> — methods that return void
Action greet = () => [Link]("Hello!");
Action<string> greetUser = name => [Link]($"Hello, {name}!");
Action<int, int> printSum = (a, b) => [Link](a + b);

greet(); // Hello!
greetUser("Alice"); // Hello, Alice!
printSum(3, 4); // 7

// Func<T..., TResult> — methods that return a value


Func<int, int, int> add = (a, b) => a + b;
Func<string, bool> isLong = s => [Link] > 10;
Func<int> random = () => new Random().Next();

[Link](add(3, 4)); // 7
[Link](isLong("hi")); // False

Multicast Delegates
A delegate variable can reference multiple methods. All are called when the delegate is invoked.
Action<string> log = msg => [Link]("[Console] " + msg);
log += msg => [Link]("[Link]", msg + "\n"); // also log to file
log += msg => [Link]("[Debug] " + msg);

log("Error occurred"); // Calls all three

Delegates as Parameters (Callbacks)


// Accept any matching method as a parameter
void Process(List<int> numbers, Func<int, bool> filter, Action<int> onMatch)
{
foreach (int n in numbers)
if (filter(n))

Page 29 of 77
C# Complete Developer Guide — Intermediate to Advanced

onMatch(n);
}

// Different behaviors by passing different delegates


Process(
new List<int> { 1, -2, 3, -4, 5 },
n => n > 0, // filter: positives
n => [Link](n) // action: print
);
// Prints: 1, 3, 5

18. Local Functions vs Lambda Expressions


Both let you define functions inside other methods. They have different tradeoffs.

Lambda Expressions
Compact, expression-based, often used inline.
// Inline in LINQ
var evens = [Link](n => n % 2 == 0).ToList();

// Assigned to a variable
Func<int, int> square = x => x * x;
Func<int, int, int> add = (a, b) => a + b;

// Multi-line lambda
Func<int, string> classify = n =>
{
if (n < 0) return "negative";
if (n == 0) return "zero";
return "positive";
};

Local Functions
Named methods defined inside another method. Better for recursion, clearer for multi-line logic.
List<int> GetPrimes(int max)
{
var primes = new List<int>();

for (int n = 2; n <= max; n++)


if (IsPrime(n))
[Link](n);

return primes;

// Local function — only visible in GetPrimes

Page 30 of 77
C# Complete Developer Guide — Intermediate to Advanced

bool IsPrime(int n)
{
for (int i = 2; i <= [Link](n); i++)
if (n % i == 0) return false;
return true;
}
}

When to Use Each


Lambda:
- Short, single-expression logic
- Passing to LINQ or higher-order functions
- When you don't need recursion or named debugging stack frames

Local function:
- Multi-line, complex logic
- When the helper is recursive
- Better debuggability (named in stack traces)
- When you want static (preventing closure capture)
// Local function with static keyword — cannot capture outer variables
// Prevents accidental captures, slightly more performant
int OuterValue = 10;

static int Pure(int x) => x * x; // Cannot accidentally use OuterValue

19. Events
Events are a publisher-subscriber mechanism built on top of delegates. They enforce that only the
owning class can invoke them.

Defining and Raising Events


public class Button
{
// Event declaration — subscribers can only add/remove, not invoke directly
public event Action? Clicked;
public event Action<string>? TextChanged;

private string text = "";

public string Text


{
get => text;
set
{
if (text != value)

Page 31 of 77
C# Complete Developer Guide — Intermediate to Advanced

{
text = value;
TextChanged?.Invoke(value); // Only Button can raise this
}
}
}

public void SimulateClick()


{
Clicked?.Invoke(); // Only Button can raise this
}
}

Subscribing and Unsubscribing


var button = new Button();

// Subscribe
[Link] += OnButtonClicked;
[Link] += (newText) => [Link]($"Text: {newText}");

// Unsubscribe — always unsubscribe when done to prevent memory leaks


[Link] -= OnButtonClicked;

void OnButtonClicked()
{
[Link]("Button was clicked!");
}

EventHandler Pattern (Standard .NET Convention)


// EventArgs subclass for custom data
public class OrderEventArgs : EventArgs
{
public int OrderId { get; }
public decimal Total { get; }
public OrderEventArgs(int id, decimal total) { OrderId = id; Total = total; }
}

public class OrderService


{
// Standard event pattern: sender + EventArgs
public event EventHandler<OrderEventArgs>? OrderPlaced;
public event EventHandler? OrderCancelled;

public void PlaceOrder(int orderId, decimal total)


{
// Process...

Page 32 of 77
C# Complete Developer Guide — Intermediate to Advanced

OrderPlaced?.Invoke(this, new OrderEventArgs(orderId, total));


}

public void CancelOrder(int orderId)


{
// Process...
OrderCancelled?.Invoke(this, [Link]);
}
}

// Subscriber
var service = new OrderService();
[Link] += (sender, e) =>
{
[Link]($"Order {[Link]} placed! Total: {[Link]:C}");
};

Events vs Delegates: Key Difference


// With a public delegate field — anyone can invoke it
public Action? OnClick; // DANGEROUS — any code can call OnClick()

// With event — only the declaring class can invoke


public event Action? OnClick; // safe — subscribers can only +=/−=

// Outside the class:


[Link] = null; // Can CLEAR a delegate field
[Link]?.Invoke(); // Can INVOKE a delegate field

[Link] = null; // ERROR — can't assign to event from outside


[Link]?.Invoke(); // ERROR — can't invoke event from outside

20. Generics
Generics let you write type-safe code that works with any type, decided at compile time without boxing
overhead.

Generic Methods
// Without generics — one method per type, or uses object (boxing)
void SwapInt(ref int a, ref int b) { int tmp = a; a = b; b = tmp; }
void SwapString(ref string a, ref string b) { string tmp = a; a = b; b = tmp; }

// With generics — one method for all types


void Swap<T>(ref T a, ref T b)
{
T tmp = a; a = b; b = tmp;

Page 33 of 77
C# Complete Developer Guide — Intermediate to Advanced

int x = 1, y = 2;
Swap(ref x, ref y); // T inferred as int
[Link]($"{x}, {y}"); // 2, 1

string s1 = "hello", s2 = "world";


Swap(ref s1, ref s2); // T inferred as string
[Link]($"{s1}, {s2}"); // world, hello

Generic Classes
public class Result<T>
{
public bool IsSuccess { get; }
public T? Value { get; }
public string? Error { get; }

private Result(bool success, T? value, string? error)


{
IsSuccess = success;
Value = value;
Error = error;
}

public static Result<T> Success(T value) => new(true, value, null);


public static Result<T> Failure(string error) => new(false, default, error);
}

Result<int> Parse(string s)
{
if ([Link](s, out int n))
return Result<int>.Success(n);
return Result<int>.Failure($"'{s}' is not a number");
}

var result = Parse("42");


if ([Link])
[Link]($"Parsed: {[Link]}"); // Parsed: 42

Generic Constraints
// T must implement IComparable<T>
T Max<T>(T a, T b) where T : IComparable<T>
=> [Link](b) >= 0 ? a : b;

// T must be a class (reference type)


void SetNull<T>(ref T? obj) where T : class

Page 34 of 77
C# Complete Developer Guide — Intermediate to Advanced

=> obj = null;

// T must have a parameterless constructor


T CreateNew<T>() where T : new()
=> new T();

// Multiple constraints
T Create<T>() where T : class, IDisposable, new() => new T();

// T must be a specific base type


void Process<T>(T animal) where T : Animal
=> [Link]();

Generic Interfaces
public interface IRepository<T> where T : class
{
T? GetById(int id);
IEnumerable<T> GetAll();
void Add(T entity);
void Update(T entity);
void Delete(int id);
}

public class UserRepository : IRepository<User>


{
private readonly List<User> users = new();

public User? GetById(int id) => [Link](u => [Link] == id);


public IEnumerable<User> GetAll() => [Link]();
public void Add(User user) => [Link](user);
public void Update(User user) {
int idx = [Link](u => [Link] == [Link]);
if (idx >= 0) users[idx] = user;
}
public void Delete(int id) => [Link](u => [Link] == id);
}

21. Constants and readonly


Both prevent modification, but in different ways and at different times.

const
Replaced by the literal value at compile time. Must be a primitive type or string.
public class MathConstants
{

Page 35 of 77
C# Complete Developer Guide — Intermediate to Advanced

public const double Pi = 3.14159265358979;


public const int MaxRetries = 3;
public const string AppName = "MyApp";
}

// Usage
double area = [Link] * r * r;

// const is implicitly static


// [Link] — accessed on type, not instance

readonly
Assigned once at runtime — either when declared or in the constructor. Works with any type.
public class Config
{
public readonly string ConnectionString;
public readonly DateTime StartTime = [Link]; // set at declaration
public readonly List<string> AllowedRoles; // reference type is OK

public Config(string connectionString, IEnumerable<string> roles)


{
ConnectionString = connectionString; // set in constructor
AllowedRoles = new List<string>(roles);
}

public void AddRole(string role)


{
[Link](role); // The LIST can be modified
// AllowedRoles = new List<string>(); ERROR — can't reassign
}
}

readonly struct and readonly members (C# 8+)


public readonly struct Coordinate
{
public readonly double Latitude;
public readonly double Longitude;

public Coordinate(double lat, double lon)


{
Latitude = lat;
Longitude = lon;
}

public double DistanceTo(Coordinate other)


{

Page 36 of 77
C# Complete Developer Guide — Intermediate to Advanced

// Calculate great-circle distance


return 0; // simplified
}
}

const vs readonly
Comparison
Feature
const
readonly
Set when
Compile time
Constructor or declaration
Types allowed
Primitives, string
Any type
Memory
Inlined as literal
Stored in memory
static
Implicitly static
Can be instance or static
Use for
Math constants, config keys
Runtime-determined values

22. Exceptions, Try/Catch/Finally


Exceptions are the mechanism for signaling and handling error conditions in C#.

Page 37 of 77
C# Complete Developer Guide — Intermediate to Advanced

Basic Try/Catch
try
{
int[] arr = { 1, 2, 3 };
[Link](arr[10]); // Throws IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
[Link]($"Array error: {[Link]}");
}
catch (Exception ex) // Catches any remaining exception
{
[Link]($"Unexpected error: {[Link]}");
}
finally
{
// Always runs — cleanup code goes here
[Link]("Done.");
}

Multiple Catch Blocks


string ReadConfig(string path)
{
try
{
return [Link](path);
}
catch (FileNotFoundException)
{
[Link]("Config file missing, using defaults.");
return DefaultConfig;
}
catch (UnauthorizedAccessException)
{
throw; // Re-throw — caller must handle permission issues
}
catch (IOException ex)
{
throw new ConfigException("Failed to read config", ex); // Wrap in domain exception
}
}

Custom Exceptions
public class InsufficientFundsException : Exception
{
public decimal Amount { get; }

Page 38 of 77
C# Complete Developer Guide — Intermediate to Advanced

public decimal Balance { get; }

public InsufficientFundsException(decimal amount, decimal balance)


: base($"Cannot withdraw {amount:C}. Balance: {balance:C}")
{
Amount = amount;
Balance = balance;
}
}

public class BankAccount


{
private decimal balance;

public void Withdraw(decimal amount)


{
if (amount > balance)
throw new InsufficientFundsException(amount, balance);

balance -= amount;
}
}

// Caller
try
{
[Link](1000);
}
catch (InsufficientFundsException ex)
{
[Link]($"Denied. You need {[Link] - [Link]:C} more.");
}

When to Use Exceptions


Throw exceptions for:
- Unexpected failures (file not found, network down)
- Programming errors (null argument, out of range)
- Conditions the caller cannot reasonably predict

Do NOT throw exceptions for:


- Normal control flow (user enters wrong password — use bool return)
- Expected outcomes (user types invalid number — return false)
- Performance-sensitive hot paths
// BAD — exception for control flow
bool IsValidAge(string input)
{
try { return [Link](input) >= 18; }

Page 39 of 77
C# Complete Developer Guide — Intermediate to Advanced

catch { return false; }


}

// GOOD — TryParse for expected failures


bool IsValidAge(string input)
=> [Link](input, out int age) && age >= 18;

23. IEnumerable
IEnumerable<T> is the interface for any sequence you can iterate with foreach. It is the foundation of
LINQ.

How foreach Works


// This foreach:
foreach (int n in numbers)
[Link](n);

// Translates to this:
IEnumerator<int> enumerator = [Link]();
while ([Link]())
[Link]([Link]);

Implementing IEnumerable with yield


yield return creates a lazy, on-demand sequence. Values are generated one at a time, only when
requested.
// Generate infinite Fibonacci sequence lazily
IEnumerable<long> Fibonacci()
{
long a = 0, b = 1;
while (true)
{
yield return a;
(a, b) = (b, a + b);
}
}

// Only compute as many as we need


foreach (long fib in Fibonacci().Take(10))
[Link](fib + " ");
// 0 1 1 2 3 5 8 13 21 34

// Filtered even numbers in range


IEnumerable<int> EvenNumbers(int from, int to)
{
for (int i = from; i <= to; i++)

Page 40 of 77
C# Complete Developer Guide — Intermediate to Advanced

if (i % 2 == 0)
yield return i;
}

Lazy Evaluation Benefit


// Without lazy evaluation — loads ALL million records
List<Record> allRecords = [Link](); // 1M objects in memory
var result = [Link](r => [Link]).Take(10).ToList();

// With IEnumerable — stops after 10


IEnumerable<Record> allRecords = [Link](); // lazy
var result = [Link](r => [Link]).Take(10).ToList();
// Only fetches enough rows to find 10 active records

Custom Collection with IEnumerable


public class NumberRange : IEnumerable<int>
{
private readonly int start, end;

public NumberRange(int start, int end) { [Link] = start; [Link] = end; }

public IEnumerator<int> GetEnumerator()


{
for (int i = start; i <= end; i++)
yield return i;
}

[Link] [Link]()
=> GetEnumerator(); // Non-generic version required
}

var range = new NumberRange(1, 5);


foreach (int n in range)
[Link](n + " "); // 1 2 3 4 5

// Also works with LINQ


int sum = new NumberRange(1, 100).Sum(); // 5050

24. Namespaces
Namespaces organize code into logical groups and prevent naming conflicts.

Page 41 of 77
C# Complete Developer Guide — Intermediate to Advanced

Declaring Namespaces
namespace [Link]
{
public class UserRepository
{
// ...
}
}

namespace [Link]
{
public class UserService
{
// ...
}
}

File-Scoped Namespace (C# 10+)


Less indentation, cleaner files.
namespace [Link]; // applies to entire file — no braces needed

public class UserService


{
// ...
}

Using Directives
using System;
using [Link];
using [Link];

// Alias to resolve conflicts


using Model = [Link];
using ViewModel = [Link];

Model user = new Model();


ViewModel vm = new ViewModel();

// Global using (C# 10+) — applies to entire project


// In [Link]:
global using System;
global using [Link];
global using [Link];

Page 42 of 77
C# Complete Developer Guide — Intermediate to Advanced

Namespace Best Practices


- Mirror folder structure: MyApp/Services/[Link] → namespace [Link]
- Use PascalCase: [Link] (not [Link])
- Match company/product structure: [Link]
- Avoid single-level namespaces — too generic
- Don't repeat namespace in type names: [Link] → [Link]

25. Singleton Pattern


The Singleton pattern ensures a class has exactly one instance and provides global access to it.

Basic Implementation
public class GameSettings
{
private static GameSettings? instance;
private static readonly object lockObj = new();

// Private constructor prevents external instantiation


private GameSettings()
{
Volume = 0.8f;
Difficulty = "Normal";
}

public static GameSettings Instance


{
get
{
if (instance == null)
{
lock (lockObj)
{
instance ??= new GameSettings(); // Double-check locking
}
}
return instance;
}
}

public float Volume { get; set; }


public string Difficulty { get; set; }
}

// Usage

Page 43 of 77
C# Complete Developer Guide — Intermediate to Advanced

[Link] = 0.5f;
[Link]([Link]);

Simpler Thread-Safe Singleton


public class AppConfig
{
// Guaranteed thread-safe by the .NET runtime
private static readonly AppConfig instance = new();

private AppConfig() { }

public static AppConfig Instance => instance;

public string ApiUrl { get; set; } = "[Link]


}

When Singleton Is Appropriate


Good uses: Configuration, logging, cache, thread pool, hardware managers.
Bad uses: Business logic, services that need to be testable or swapped.

// Singleton makes testing hard — consider Dependency Injection instead


public class OrderService
{
public void PlaceOrder(Order order)
{
// Tight coupling to singleton — can't inject a test logger
[Link]("Order placed");
[Link](order);
}
}

// Better — inject dependencies


public class OrderService
{
private readonly ILogger logger;
private readonly IDatabase database;

public OrderService(ILogger logger, IDatabase database)


{
[Link] = logger;
[Link] = database;
}

public void PlaceOrder(Order order)


{
[Link]("Order placed");
[Link](order);

Page 44 of 77
C# Complete Developer Guide — Intermediate to Advanced

}
}

26. Design Patterns Overview


Design patterns are proven solutions to common software design problems.

Creational Patterns
Factory Method — define an interface for creating objects; subclasses decide which to instantiate.
public abstract class Notification
{
public abstract void Send(string message);

// Factory method
public static Notification Create(string type) => type switch
{
"email" => new EmailNotification(),
"sms" => new SmsNotification(),
"push" => new PushNotification(),
_ => throw new ArgumentException($"Unknown type: {type}")
};
}

Notification n = [Link]("email");
[Link]("Hello!");
Builder — construct complex objects step by step.
var email = new EmailBuilder()
.To("alice@[Link]")
.Subject("Welcome")
.Body("Hello, Alice!")
.WithAttachment("[Link]")
.Build();

Structural Patterns
Decorator — add behavior to objects without subclassing.
public interface IDataSource { string Read(); void Write(string data); }

public class FileDataSource : IDataSource { /* base */ }

public class EncryptionDecorator : IDataSource


{
private readonly IDataSource wrapped;
public EncryptionDecorator(IDataSource source) => wrapped = source;

public void Write(string data) => [Link](Encrypt(data));

Page 45 of 77
C# Complete Developer Guide — Intermediate to Advanced

public string Read() => Decrypt([Link]());

private string Encrypt(string s) => /* encrypt */ s;


private string Decrypt(string s) => /* decrypt */ s;
}

// Stack decorators
IDataSource source = new EncryptionDecorator(
new CompressionDecorator(
new FileDataSource("[Link]")
)
);
[Link]("sensitive data");

Behavioral Patterns
Observer — notify dependents when state changes (see Events section).
Strategy — define family of algorithms, make them interchangeable.

public interface ISortStrategy { void Sort(List<int> data); }

public class QuickSort : ISortStrategy


{
public void Sort(List<int> data) { /* quicksort */ }
}

public class MergeSort : ISortStrategy


{
public void Sort(List<int> data) { /* mergesort */ }
}

public class Sorter


{
private ISortStrategy strategy;
public Sorter(ISortStrategy strategy) => [Link] = strategy;
public void SetStrategy(ISortStrategy s) => strategy = s;
public void Sort(List<int> data) => [Link](data);
}
Command — encapsulate an operation as an object.
public interface ICommand { void Execute(); void Undo(); }

public class MoveCommand : ICommand


{
private readonly Player player;
private readonly Vector2 delta;
private Vector2 previousPosition;

public MoveCommand(Player p, Vector2 d) { player = p; delta = d; }

Page 46 of 77
C# Complete Developer Guide — Intermediate to Advanced

public void Execute() { previousPosition = [Link]; [Link] += delta; }


public void Undo() => [Link] = previousPosition;
}

// Undo/redo system
var history = new Stack<ICommand>();
void Do(ICommand cmd) { [Link](); [Link](cmd); }
void UndoLast() { if ([Link] > 0) [Link]().Undo(); }

ADVANCED

27. Reflection
Reflection allows code to inspect and interact with its own types at runtime — reading metadata,
invoking methods by name, creating instances dynamically.

Inspecting Types
using [Link];

Type type = typeof(string);

[Link]([Link]); // String
[Link]([Link]); // System
[Link]([Link]); // True

// Get all methods


foreach (MethodInfo method in [Link]())
[Link]([Link]);

// Get specific method


MethodInfo? toUpper = [Link]("ToUpper", [Link]);
[Link](toUpper?.ReturnType); // [Link]

Creating Instances at Runtime


Type type = [Link]("[Link]`1[System.Int32]")!;
object? instance = [Link](type);
[Link](instance?.GetType().Name); // List`1

// From assembly
Assembly assembly = [Link]();

Page 47 of 77
C# Complete Developer Guide — Intermediate to Advanced

Type? myType = [Link]("[Link]");


object? obj = [Link](myType!);

Invoking Methods Dynamically


class Calculator
{
public int Add(int a, int b) => a + b;
private string Secret() => "hidden";
}

var calc = new Calculator();


Type type = [Link]();

MethodInfo? addMethod = [Link]("Add");


object? result = addMethod?.Invoke(calc, new object[] { 3, 4 });
[Link](result); // 7

// Invoke private method (use with caution)


MethodInfo? secret = [Link]("Secret",
[Link] | [Link]);
[Link](secret?.Invoke(calc, null)); // "hidden"

Reading Properties and Fields


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

var p = new Person { Name = "Alice", Age = 30 };

foreach (PropertyInfo prop in [Link]().GetProperties())


[Link]($"{[Link]} = {[Link](p)}");

// Set a property by name


PropertyInfo? nameProp = [Link]().GetProperty("Name");
nameProp?.SetValue(p, "Bob");
[Link]([Link]); // Bob

Practical Use: Generic Serializer


string Serialize(object obj)
{
var parts = new List<string>();
foreach (PropertyInfo prop in [Link]().GetProperties())
{
object? value = [Link](obj);
[Link]($"{[Link]}={value}");
}
return [Link](";", parts);
}

Page 48 of 77
C# Complete Developer Guide — Intermediate to Advanced

var p = new Person { Name = "Alice", Age = 30 };


[Link](Serialize(p)); // Name=Alice;Age=30

28. Extension Methods


Extension methods add new methods to existing types without modifying their source or inheriting from
them.

Declaring Extension Methods


The class must be static. The first parameter is the extended type, prefixed with this.
public static class StringExtensions
{
// Extends string
public static bool IsNullOrEmpty(this string? s)
=> [Link](s);

public static string TruncateAt(this string s, int maxLength)


=> [Link] <= maxLength ? s : s[..maxLength] + "...";

public static string ToTitleCase(this string s)


=> [Link]
.[Link]([Link]());
}

// Usage — looks like instance methods


string title = "hello world".ToTitleCase(); // "Hello World"
string truncated = "This is a long string".TruncateAt(10); // "This is a ..."

Extending Collections
public static class CollectionExtensions
{
public static bool IsNullOrEmpty<T>(this ICollection<T>? collection)
=> collection == null || [Link] == 0;

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)


{
var rng = new Random();
return [Link](_ => [Link]());
}

public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)


{
foreach (T item in source)
action(item);

Page 49 of 77
C# Complete Developer Guide — Intermediate to Advanced

}
}

List<int> nums = new() { 1, 2, 3, 4, 5 };


[Link]().ForEach(n => [Link](n + " "));

Extending Interfaces
This is particularly powerful — you can add behavior to ALL implementations of an interface.
public static class RepositoryExtensions
{
public static bool Exists<T>(this IRepository<T> repo, int id) where T : class
=> [Link](id) != null;

public static int Count<T>(this IRepository<T> repo) where T : class


=> [Link]().Count();
}

// Now ANY IRepository has Exists() and Count()


bool userExists = [Link](42);

29. Static Constructors


A static constructor runs once, automatically, before the type is first used. You cannot call it explicitly or
give it parameters.
public class AppConfiguration
{
public static readonly string ConnectionString;
public static readonly string ApiKey;
public static readonly Dictionary<string, string> Settings;

// Runs once, before first use of AppConfiguration


static AppConfiguration()
{
[Link]("Static constructor running...");

ConnectionString = [Link]("DB_CONNECTION")
?? "Server=localhost;Database=app;";

ApiKey = [Link]("[Link]")
? [Link]("[Link]").Trim()
: throw new InvalidOperationException("[Link] file missing");

Settings = new Dictionary<string, string>


{
["theme"] = "dark",

Page 50 of 77
C# Complete Developer Guide — Intermediate to Advanced

["locale"] = "en-US"
};
}
}

// Static constructor runs before this:


[Link]([Link]);

Rules
Runs automatically before first access to any static member or instance creation
No parameters, no access modifier
Only one per class
Cannot be called manually
Exceptions in static constructors cause TypeInitializationException

30. typeof, nameof, sizeof, default


These keywords provide compile-time information about types and members.

typeof — Get Type Object at Compile Time


Type t = typeof(int);
[Link]([Link]); // Int32
[Link]([Link]); // System.Int32
[Link]([Link]); // True

// Useful in generics and reflection


bool Is<T>(object obj) => [Link]() == typeof(T);
[Link](Is<int>(42)); // True
[Link](Is<int>("hello")); // False

// Different from GetType() — typeof works at compile time on type names


Type a = typeof(string); // compile-time
Type b = "hello".GetType(); // runtime — same result here, but different mechanism

nameof — Refactor-Safe Member Names


nameof returns the string name of a variable, type, or member at compile time. It refactors
automatically when you rename.
// Without nameof — fragile
void SetValue(int newValue)
{
if (newValue < 0)
throw new ArgumentException("newValue cannot be negative."); // typo risk
}

Page 51 of 77
C# Complete Developer Guide — Intermediate to Advanced

// With nameof — always correct, refactor-safe


void SetValue(int newValue)
{
if (newValue < 0)
throw new ArgumentException("Cannot be negative.", nameof(newValue));
}

// Property change notification


private int age;
public int Age
{
get => age;
set
{
age = value;
OnPropertyChanged(nameof(Age)); // Not "Age" string — refactor-safe
}
}

// Log method names


void ProcessData()
{
[Link]($"Entering {nameof(ProcessData)}");
}

sizeof — Size of Value Types


[Link](sizeof(int)); // 4 bytes
[Link](sizeof(double)); // 8 bytes
[Link](sizeof(char)); // 2 bytes
[Link](sizeof(bool)); // 1 byte

// For custom structs, requires unsafe context


// unsafe { [Link](sizeof(MyStruct)); }

default — Default Value of a Type


[Link](default(int)); // 0
[Link](default(bool)); // False
[Link](default(string)); // (null)
[Link](default(char)); // \0 (null character)

// In generics
T GetDefault<T>() => default(T);
// Or simply:
T GetDefault<T>() => default; // C# 7.1+

Page 52 of 77
C# Complete Developer Guide — Intermediate to Advanced

31. Expression-Bodied Members


Expression-bodied members use => to define a method or property body as a single expression.
public class Person
{
public string FirstName { get; }
public string LastName { get; }

public Person(string firstName, string lastName)


{
FirstName = firstName;
LastName = lastName;
}

// Expression-bodied property
public string FullName => $"{FirstName} {LastName}";

// Expression-bodied method
public string Greet() => $"Hello, I'm {FullName}.";

// Expression-bodied constructor (unusual but valid)


// Only usable in constructors with single-assignment-like logic

// Expression-bodied read-only property with logic


public bool IsAdult => /* age */ 18 >= 18;

// Expression-bodied override
public override string ToString() => FullName;
}

// In interfaces, abstract classes


public abstract class Shape
{
public abstract double Area { get; }
public virtual string Describe() => $"A shape with area {Area:F2}";
}

public class Circle : Shape


{
public double Radius { get; }
public Circle(double r) => Radius = r;

public override double Area => [Link] * Radius * Radius;


}

Page 53 of 77
C# Complete Developer Guide — Intermediate to Advanced

32. Records
Records (C# 9+) are classes or structs designed for immutable data with built-in equality, hashing, and
string representation.

Record Class
// This single line generates:
// - Constructor, properties
// - Equals(), GetHashCode(), ToString()
// - With expression support
public record Point(double X, double Y);

var p1 = new Point(1, 2);


var p2 = new Point(1, 2);

[Link](p1 == p2); // True — structural equality


[Link]([Link]()); // Point { X = 1, Y = 2 }

// With expression — create a copy with changed properties


Point p3 = p1 with { X = 10 };
[Link](p3); // Point { X = 10, Y = 2 }
[Link](p1); // Point { X = 1, Y = 2 } — unchanged

Record vs Class
// Class equality = reference equality (same object)
var c1 = new PersonClass("Alice", 30);
var c2 = new PersonClass("Alice", 30);
[Link](c1 == c2); // False — different objects

// Record equality = value equality (same data)


var r1 = new PersonRecord("Alice", 30);
var r2 = new PersonRecord("Alice", 30);
[Link](r1 == r2); // True — same data

public record PersonRecord(string Name, int Age);

Records with Validation


public record Temperature
{
public double Celsius { get; init; }

public Temperature(double celsius)


{
if (celsius < -273.15)
throw new ArgumentException("Below absolute zero.");
Celsius = celsius;

Page 54 of 77
C# Complete Developer Guide — Intermediate to Advanced

public double Fahrenheit => Celsius * 9 / 5 + 32;


public double Kelvin => Celsius + 273.15;
}

33. Null Conditional and Null Coalescing Operators


These operators handle null values concisely and safely.

Null-Conditional Operator ?. and ?[]


Short-circuits to null if the left side is null, instead of throwing NullReferenceException.
string? name = user?.Profile?.DisplayName; // null if any is null
int? length = name?.Length; // null if name is null

// On collections
string? first = list?[0]; // null if list is null
int? count = list?.Count; // null if list is null

// Invoke delegate safely


OnUpdate?.Invoke(42); // won't throw if OnUpdate is null

Null-Coalescing Operator ??
Returns the left side if not null, otherwise the right side.
string name = [Link] ?? "Anonymous";
int timeout = [Link] ?? 30;
var items = GetItems() ?? new List<string>();

// Chained
string val = a ?? b ?? c ?? "default";

Null-Coalescing Assignment ??=


Assigns only if the variable is currently null.
List<string>? cache = null;

// Old way
if (cache == null) cache = new List<string>();

// New way
cache ??= new List<string>();

// Useful for lazy initialization


private List<string>? cachedResults;
public List<string> Results => cachedResults ??= LoadResults();

Page 55 of 77
C# Complete Developer Guide — Intermediate to Advanced

Combining Them
// Without null operators
string displayName;
if (user != null && [Link] != null && [Link] != null)
displayName = [Link];
else
displayName = "Guest";

// With null operators — same logic


string displayName = user?.Profile?.Name ?? "Guest";

34. Ternary Conditional Operator


A compact if-else expression: condition ? valueIfTrue : valueIfFalse
int age = 20;
string category = age >= 18 ? "Adult" : "Minor";

// Nested (use sparingly — readability drops fast)


string label = score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : "F";

// In method arguments
[Link](count == 1 ? "1 item" : $"{count} items");

// Null checks
string name = user != null ? [Link] : "Unknown";
// Better with ?? if result is about null fallback:
string name2 = user?.Name ?? "Unknown";

35. Nullable Types


C# distinguishes between nullable and non-nullable references (since C# 8 with Nullable Reference
Types enabled).

Nullable Value Types


int? x = null; // Nullable<int>
double? d = null;

// Checking
if ([Link])
[Link]([Link]);

// Coalescing
int value = x ?? 0;

Page 56 of 77
C# Complete Developer Guide — Intermediate to Advanced

// GetValueOrDefault
int v = [Link](); // 0 if null
int v2 = [Link](99); // 99 if null

Nullable Reference Types (C# 8+)


Enable with <Nullable>enable</Nullable> in .csproj. Then the compiler warns about potential null
dereferences.
string name = "Alice"; // Cannot be null
string? optionalName = null; // Can be null

void Greet(string name) // Caller must pass non-null


{
[Link]([Link]); // Safe — compiler knows name is not null
}

void Log(string? message) // Nullable parameter


{
[Link](message?.Length ?? 0); // Must handle null
}

Null Forgiving Operator !


Tells the compiler “I know this isn’t null, trust me.”
string? maybeNull = GetValue();

// Compiler warning: possible null dereference


int length = [Link]; // Warning

// Null forgiving — you're certain it's not null


int length2 = maybeNull!.Length; // No warning

// Use sparingly — hides real null bugs

36. Span
Span<T> is a stack-allocated, array-like type for high-performance slicing of memory without allocations.

Basic Usage
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8 };

// Span over existing array — no copy


Span<int> span = array;
Span<int> middle = [Link](2, 4); // Elements 3,4,5,6

Page 57 of 77
C# Complete Developer Guide — Intermediate to Advanced

middle[0] = 99;
[Link](array[2]); // 99 — same memory

// Span over a string (ReadOnlySpan<char>)


string text = "Hello, World!";
ReadOnlySpan<char> slice = [Link](7, 5); // "World"
[Link]([Link]());

Why Span?
// Without Span — allocates new string
string input = "2024-01-15";
string year = [Link](0, 4); // NEW string allocation
string month = [Link](5, 2); // NEW string allocation

// With Span — zero allocations


ReadOnlySpan<char> span = [Link]();
ReadOnlySpan<char> year = [Link](0, 4); // No allocation
ReadOnlySpan<char> month = [Link](5, 2); // No allocation
int y = [Link](year); // Parse directly from span
int m = [Link](month);

Span Limitations
Cannot be stored in heap fields (class members)
Cannot be used with async/await
Cannot be boxed
Designed for synchronous, stack-based processing

37. Bitwise Operators


Bitwise operators work on individual bits of integer values. Essential for flags, low-level code, and
performance-sensitive operations.

The Operators
int a = 0b1010; // 10
int b = 0b1100; // 12

[Link](a & b); // AND: 0b1000 = 8 (both bits must be 1)


[Link](a | b); // OR: 0b1110 = 14 (either bit is 1)
[Link](a ^ b); // XOR: 0b0110 = 6 (exactly one bit is 1)
[Link](~a); // NOT: all bits flipped = -11
[Link](a << 1); // Left shift: 0b10100 = 20 (multiply by 2)
[Link](a >> 1); // Right shift: 0b0101 = 5 (divide by 2)

Page 58 of 77
C# Complete Developer Guide — Intermediate to Advanced

Practical Uses
// Fast multiply/divide by powers of 2
int x = 8;
int doubled = x << 1; // 16 — faster than x * 2 on some platforms
int halved = x >> 1; // 4

// Check if number is even/odd


bool isEven = (x & 1) == 0; // Last bit is 0 → even
bool isOdd = (x & 1) == 1;

// Swap without temp variable


int p = 5, q = 9;
p ^= q;
q ^= p;
p ^= q;
// p = 9, q = 5

// Extract RGB from packed color


int color = 0xFF3366AA;
int alpha = (color >> 24) & 0xFF; // FF = 255
int red = (color >> 16) & 0xFF; // 33 = 51
int green = (color >> 8) & 0xFF; // 66 = 102
int blue = color & 0xFF; // AA = 170

38. Enum Flags


Flags allow a single enum variable to represent multiple selected values simultaneously, using bitwise
OR.
[Flags] // Attribute affects ToString() output
public enum Permissions
{
None = 0,
Read = 1, // 0001
Write = 2, // 0010
Execute = 4, // 0100
Admin = 8, // 1000
All = Read | Write | Execute | Admin // 1111 = 15
}

// Combine flags with |


Permissions userPerms = [Link] | [Link];

// Check a flag with &


bool canRead = (userPerms & [Link]) != 0; // True
bool canExec = (userPerms & [Link]) != 0; // False

Page 59 of 77
C# Complete Developer Guide — Intermediate to Advanced

// HasFlag (cleaner syntax)


bool canWrite = [Link]([Link]); // True

// Add a flag
userPerms |= [Link];

// Remove a flag
userPerms &= ~[Link];

// Toggle a flag
userPerms ^= [Link];

[Link](userPerms); // Execute — reads individual flags

39. Preprocessor Directives


Preprocessor directives tell the compiler to include or exclude blocks of code based on conditions.
#define DEBUG_MODE // Define a symbol

// Conditional compilation
#if DEBUG_MODE
[Link]("Debug build — extra logging enabled.");
#elif RELEASE
[Link]("Release build.");
#else
[Link]("Unknown build.");
#endif

// Common built-in symbols


#if DEBUG
[Link]("Debug configuration");
#endif

#if UNITY_EDITOR
// Unity editor-only code
#endif

// Platform-specific
#if WINDOWS
// Windows code
#elif LINUX
// Linux code
#endif

// Suppress warnings
#pragma warning disable CS0168 // Unused variable

Page 60 of 77
C# Complete Developer Guide — Intermediate to Advanced

int unused;
#pragma warning restore CS0168

// Regions (organize code visually — controversial, used sparingly)


#region Event Handlers
void OnClick() { }
void OnHover() { }
#endregion

40. ref, out, in


These modifiers control how parameters pass data between caller and called method.

ref — Two-Way Passing


Passes a reference to the variable. Changes affect the caller’s variable.
void Double(ref int value) => value *= 2;

int n = 5;
Double(ref n);
[Link](n); // 10 — modified

// Must be initialized before passing


// int x;
// Double(ref x); ERROR — x not assigned

out — Return Multiple Values


Like ref but the method must assign a value before returning. Variable doesn’t need to be initialized.
bool TryParse(string input, out int result, out string error)
{
if ([Link](input, out result))
{
error = "";
return true;
}
result = 0;
error = $"'{input}' is not a number";
return false;
}

if (TryParse("42", out int value, out string err))


[Link]($"Parsed: {value}");
else
[Link]($"Error: {err}");

// Discard out params you don't need

Page 61 of 77
C# Complete Developer Guide — Intermediate to Advanced

if ([Link]("42", out _))


[Link]("Valid number");

in — Read-Only Reference
Passes a reference (no copy) but prevents modification. Performance optimization for large structs.
// Without in — copies 64 bytes on each call
double CalculateDot(Vector4 a, Vector4 b)
=> a.X*b.X + a.Y*b.Y + a.Z*b.Z + a.W*b.W;

// With in — passes reference, prevents writes, no copy


double CalculateDot(in Vector4 a, in Vector4 b)
=> a.X*b.X + a.Y*b.Y + a.Z*b.Z + a.W*b.W;

// a.X = 5; ERROR — in parameter is read-only

41. Data Boxing


Boxing converts a value type to object (heap allocation). Unboxing reverses it. Both are expensive.
// Boxing — creates heap object
int value = 42;
object boxed = value; // Allocates new object on heap, copies value

// Unboxing — extracts value


int unboxed = (int)boxed; // Must cast to correct type

// InvalidCastException if type is wrong


// double d = (double)boxed; THROWS at runtime

Hidden Boxing
// ArrayList boxes every value type (legacy)
[Link] list = new();
[Link](42); // BOXES int
[Link](3.14); // BOXES double

// Struct implementing interface — boxing!


IComparable comp = (IComparable)42; // BOXES int

// [Link] with value types


string msg = [Link]("Value: {0}", 42); // BOXES 42

// Interpolation is smarter — often avoids boxing


string msg2 = $"Value: {42}"; // May avoid boxing for common types

Page 62 of 77
C# Complete Developer Guide — Intermediate to Advanced

Avoiding Boxing
// Use generic collections — no boxing
List<int> nums = new();
[Link](42); // No boxing

// Constrained generic calls


void Print<T>(T value) where T : struct // T can't be boxed
=> [Link](value);

// Direct method calls on value types


int n = 42;
[Link](); // No boxing — compiler calls struct method directly

42. dynamic
dynamic bypasses compile-time type checking. Resolved at runtime by the Dynamic Language Runtime
(DLR).
dynamic x = 10;
[Link](x + 5); // 15 (int)

x = "hello";
[Link]([Link]); // 5 (string) — no compile error

x = new List<int> { 1, 2, 3 };
[Link](4); // Works at runtime — no intellisense though

// Errors happen at RUNTIME, not compile time


// [Link](); Compiles! But throws RuntimeBinderException

Practical Uses
// Working with COM objects (Office interop)
dynamic excel = [Link]([Link]("[Link]")!);
[Link] = true;

// Working with JSON-like structures


dynamic config = LoadDynamicConfig();
string host = [Link]; // No class needed

// Calling methods on unknown types


void CallMethod(object obj, string methodName)
{
dynamic d = obj;
[Link]().GetMethod(methodName)?.Invoke(d, null);
// Or simply: ((dynamic)obj).MethodName(); — DLR resolves it
}

Page 63 of 77
C# Complete Developer Guide — Intermediate to Advanced

Caution
dynamic loses IntelliSense, refactoring support, and compile-time safety. Use it only when you genuinely
don’t know the type (interop, serialization, scripting). For normal C# code, use proper types or generics.

43. Attributes
Attributes attach metadata to types, methods, and members. They are read at runtime via reflection, or
at compile time by source generators.

Built-In Attributes
[Obsolete("Use NewMethod() instead.", error: false)]
void OldMethod() { } // Warning when called

[Serializable]
class SaveData { } // Marks for binary serialization

[NonSerialized]
private int runtimeCache; // Exclude from serialization

[Conditional("DEBUG")]
void LogDebug(string msg) // Only compiled in DEBUG builds
=> [Link](msg);

[ThreadStatic]
private static int threadLocalValue; // Separate copy per thread

Custom Attributes
// Define an attribute
[AttributeUsage([Link] | [Link],
AllowMultiple = false,
Inherited = true)]
public class AuthorAttribute : Attribute
{
public string Name { get; }
public string Email { get; }
public int Year { get; set; }

public AuthorAttribute(string name, string email)


{
Name = name;
Email = email;
}
}

Page 64 of 77
C# Complete Developer Guide — Intermediate to Advanced

// Apply the attribute


[Author("Alice Smith", "alice@[Link]", Year = 2024)]
public class OrderService
{
[Author("Bob Jones", "bob@[Link]")]
public void ProcessOrder() { }
}

// Read attribute via reflection


var attr = typeof(OrderService).GetCustomAttribute<AuthorAttribute>();
if (attr != null)
[Link]($"Written by {[Link]} ({[Link]})");

44. Tuples
Tuples group multiple values without creating a named type.
// Unnamed tuple
var point = (3, 4);
[Link](point.Item1); // 3
[Link](point.Item2); // 4

// Named tuple
var person = (Name: "Alice", Age: 30);
[Link]([Link]); // Alice
[Link]([Link]); // 30

// Returning multiple values from a method


(string Name, int Age) GetUser(int id)
=> ("Alice", 30);

var (name, age) = GetUser(1); // Deconstruction


[Link]($"{name}, {age}");

// Swapping values elegantly


int a = 1, b = 2;
(a, b) = (b, a);
[Link]($"a={a}, b={b}"); // a=2, b=1

// Ignoring parts with discard


var (firstName, _) = ("Alice", "Smith"); // ignore last name

Page 65 of 77
C# Complete Developer Guide — Intermediate to Advanced

45. using and IDisposable


using ensures Dispose() is called when an object goes out of scope — critical for files, database
connections, and other unmanaged resources.
// using statement — calls Dispose() at end of block
using (var file = new StreamWriter("[Link]"))
{
[Link]("Hello");
[Link]("World");
} // [Link]() called here automatically

// using declaration (C# 8+) — cleaner, same behavior


using var reader = new StreamReader("[Link]");
string content = [Link]();
// Disposed at end of enclosing scope

// Multiple resources
using var conn = new SqlConnection(connectionString);
using var cmd = new SqlCommand("SELECT ...", conn);
// Both disposed at end of scope

Implementing IDisposable
public class DatabaseConnection : IDisposable
{
private SqlConnection? connection;
private bool disposed;

public DatabaseConnection(string connectionString)


{
connection = new SqlConnection(connectionString);
[Link]();
}

public void Execute(string sql)


{
if (disposed) throw new ObjectDisposedException(nameof(DatabaseConnection));
// Execute query...
}

public void Dispose()


{
Dispose(disposing: true);
[Link](this); // No need for finalizer
}

protected virtual void Dispose(bool disposing)


{

Page 66 of 77
C# Complete Developer Guide — Intermediate to Advanced

if (!disposed)
{
if (disposing)
{
connection?.Close();
connection?.Dispose();
connection = null;
}
disposed = true;
}
}
}

using var db = new DatabaseConnection("...");


[Link]("UPDATE ...");
// [Link]() called automatically

46. LINQ
Language Integrated Query lets you query collections using a fluent, SQL-like syntax directly in C#.

Method Syntax
var nums = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// Filter
var evens = [Link](n => n % 2 == 0);

// Transform
var squares = [Link](n => n * n);

// Filter + transform
var result = nums
.Where(n => n > 3)
.Select(n => n * 2)
.ToList();

// Aggregate
int sum = [Link]();
double avg = [Link]();
int max = [Link]();
int min = [Link]();

// First/Single
int first = [Link]();
int firstEven = [Link](n => n % 2 == 0);

Page 67 of 77
C# Complete Developer Guide — Intermediate to Advanced

int? maybeFirst = [Link](n => n > 100); // null if not found

// Sorting
var sorted = [Link](n => n);
var sortedDesc = [Link](n => n);

// Distinct, Skip, Take


var unique = [Link]();
var page = [Link](3).Take(3); // { 4, 5, 6 }

// Any / All
bool anyNegative = [Link](n => n < 0); // False
bool allPositive = [Link](n => n > 0); // True

// GroupBy
var people = new List<(string Name, string City)>
{
("Alice", "NYC"), ("Bob", "LA"), ("Carol", "NYC")
};

var byCity = [Link](p => [Link]);


foreach (var group in byCity)
{
[Link]($"{[Link]}: {[Link](", ", [Link](p => [Link]))}");
}
// NYC: Alice, Carol
// LA: Bob

// Join
var orders = [Link](
invoices,
customer => [Link],
invoice => [Link],
(customer, invoice) => new { [Link], [Link] }
);

Query Syntax
var result =
from n in nums
where n % 2 == 0
orderby n descending
select n * 10;

// Equivalent to:
var result2 = nums
.Where(n => n % 2 == 0)

Page 68 of 77
C# Complete Developer Guide — Intermediate to Advanced

.OrderByDescending(n => n)
.Select(n => n * 10);

47. Overriding Operators


You can redefine operators (+, ==, <, etc.) for your types.
public struct Money
{
public decimal Amount { get; }
public string Currency { get; }

public Money(decimal amount, string currency)


{
Amount = amount;
Currency = currency;
}

// Arithmetic
public static Money operator +(Money a, Money b)
{
if ([Link] != [Link])
throw new InvalidOperationException("Cannot add different currencies.");
return new Money([Link] + [Link], [Link]);
}

public static Money operator -(Money a, Money b)


{
if ([Link] != [Link])
throw new InvalidOperationException("Cannot subtract different currencies.");
return new Money([Link] - [Link], [Link]);
}

public static Money operator *(Money m, decimal factor)


=> new Money([Link] * factor, [Link]);

// Comparison — must come in pairs


public static bool operator ==(Money a, Money b)
=> [Link] == [Link] && [Link] == [Link];

public static bool operator !=(Money a, Money b)


=> !(a == b);

public static bool operator >(Money a, Money b)


{
if ([Link] != [Link]) throw new InvalidOperationException();
return [Link] > [Link];

Page 69 of 77
C# Complete Developer Guide — Intermediate to Advanced

public static bool operator <(Money a, Money b) => b > a;

// Implicit/explicit conversions
public static implicit operator decimal(Money m) => [Link];
public static explicit operator Money(decimal amount) => new Money(amount, "USD");

public override string ToString() => $"{Currency} {Amount:F2}";


public override bool Equals(object? obj) => obj is Money m && this == m;
public override int GetHashCode() => [Link](Amount, Currency);
}

var price = new Money(10.99m, "USD");


var tax = new Money(0.88m, "USD");
Money total = price + tax; // USD 11.87
Money discounted = total * 0.9m; // USD 10.68
[Link](price > tax); // True
decimal amount = price; // Implicit — 10.99
Money m = (Money)15.00m; // Explicit

48. async / await


Asynchronous programming lets your application remain responsive while waiting for I/O, network, or
other slow operations.

The Problem async Solves


// Synchronous — BLOCKS the thread for 2 seconds
void SyncApp()
{
[Link]("Downloading...");
[Link](2000); // Thread is frozen
[Link]("Done.");
}

// Asynchronous — thread is FREE during the wait


async Task AsyncApp()
{
[Link]("Downloading...");
await [Link](2000); // Thread does other work
[Link]("Done.");
}

Page 70 of 77
C# Complete Developer Guide — Intermediate to Advanced

async and Task


// Returns nothing (fire-and-forget style — prefer returning Task)
async void VoidMethod() { await [Link](100); }

// Returns nothing but awaitable


async Task DoWorkAsync()
{
await [Link](1000);
[Link]("Work done.");
}

// Returns a value
async Task<string> GetDataAsync()
{
await [Link](1000); // Simulate network call
return "Some data";
}

// Chaining
async Task MainAsync()
{
string data = await GetDataAsync(); // Waits, then assigns
[Link](data);

await DoWorkAsync(); // Wait for void-returning work


}

HTTP Call Example


using [Link];

async Task<string> FetchUrlAsync(string url)


{
using var client = new HttpClient();

try
{
HttpResponseMessage response = await [Link](url);
[Link]();

string content = await [Link]();


return content;
}
catch (HttpRequestException ex)
{
[Link]($"Network error: {[Link]}");
return [Link];

Page 71 of 77
C# Complete Developer Guide — Intermediate to Advanced

}
}

Running Multiple Tasks in Parallel


async Task ParallelDownloads()
{
Task<string> task1 = FetchUrlAsync("[Link]
Task<string> task2 = FetchUrlAsync("[Link]
Task<string> task3 = FetchUrlAsync("[Link]

// Wait for all three — runs concurrently!


string[] results = await [Link](task1, task2, task3);

foreach (string result in results)


[Link]([Link]);
}

// WhenAny — continue when the fastest one completes


Task<string> first = await [Link](task1, task2, task3);

CancellationToken
async Task LongOperation(CancellationToken ct)
{
for (int i = 0; i < 100; i++)
{
[Link](); // Check at safe points
await [Link](100, ct); // Cancellable delay
[Link]($"Step {i}");
}
}

using var cts = new CancellationTokenSource();


[Link]([Link](3)); // Cancel after 3 seconds

try
{
await LongOperation([Link]);
}
catch (OperationCanceledException)
{
[Link]("Operation was cancelled.");
}

Page 72 of 77
C# Complete Developer Guide — Intermediate to Advanced

49. Multithreading
Multithreading lets multiple pieces of code run concurrently on different CPU cores.

Thread Basics
void PrintNumbers()
{
for (int i = 0; i < 5; i++)
{
[Link]($"Thread {[Link]}: {i}");
[Link](100);
}
}

Thread t1 = new Thread(PrintNumbers);


Thread t2 = new Thread(PrintNumbers);

[Link]();
[Link]();

[Link](); // Wait for t1 to finish


[Link](); // Wait for t2 to finish

Race Conditions and lock


When multiple threads access shared data simultaneously, results become unpredictable.
// UNSAFE — race condition
int counter = 0;
void Increment() { for (int i = 0; i < 100000; i++) counter++; }

Thread t1 = new(Increment), t2 = new(Increment);


[Link](); [Link]();
[Link](); [Link]();
[Link](counter); // NOT 200000 — race condition!

// SAFE — with lock


int counter = 0;
object lockObj = new object();

void SafeIncrement()
{
for (int i = 0; i < 100000; i++)
{
lock (lockObj) { counter++; }
}
}
// Now counter is always 200000

Page 73 of 77
C# Complete Developer Guide — Intermediate to Advanced

Thread Pool and Task


Prefer Task over raw Thread — it uses the thread pool efficiently.
// Thread pool via Task
Task t = [Link](() =>
{
[Link]("Running on thread pool");
});

await t;

// [Link] — data parallelism


[Link](0, 1000, i =>
{
Process(i); // Each iteration can run on different threads
});

// [Link]
[Link](items, item => Process(item));

Interlocked — Atomic Operations


int count = 0;

// Thread-safe atomic increment (faster than lock for simple operations)


[Link](ref count); // count++
[Link](ref count); // count--
[Link](ref count, 5); // count += 5
int old = [Link](ref count, 0); // set to 0, return old

Thread Safety Checklist


- Immutable data: no locks needed (string, readonly fields)
- lock: for short critical sections, avoid holding locks long
- Avoid nested locks: can cause deadlock
- Prefer Task/async over Thread for I/O
- Use concurrent collections: ConcurrentDictionary, ConcurrentQueue, ConcurrentBag
- Interlocked for atomic counter operations

Page 74 of 77
C# Complete Developer Guide — Intermediate to Advanced

50. Summary and Key Takeaways

Intermediate Concepts Recap


Concept
Core Use
Refactoring
Improve code quality without changing behavior
Enums
Named, type-safe sets of constants
Properties
Encapsulated, validated field access
Dictionaries
O(1) key-value lookup
Generics
Type-safe, reusable code without boxing
Delegates & Events

Page 75 of 77
C# Complete Developer Guide — Intermediate to Advanced

Callbacks and publisher-subscriber patterns


Interfaces
Contracts for polymorphism
Exceptions
Signal and handle error conditions
LINQ
Declarative querying of any sequence
Nullable
Express and handle absence of value
Tuples
Group values without named types

Advanced Concepts Recap


Concept
Core Use
Reflection
Inspect/invoke types at runtime
Extension Methods
Add methods to existing types
async/await

Page 76 of 77
C# Complete Developer Guide — Intermediate to Advanced

Non-blocking I/O without callbacks


Multithreading
Parallel CPU work, shared state
Span
Zero-allocation memory slicing
Attributes
Metadata for runtime/compile behavior
Records
Value-semantic immutable data types
LINQ
Functional data transformation
dynamic
Runtime dispatch, interop
IDisposable
Deterministic resource cleanup

Rules to Live By
Name everything clearly — code is read 10× more than it is written
Fail fast — validate inputs immediately, throw early
Prefer immutability — readonly, init, record
Avoid boxing — use generics, not object
Always unsubscribe events — or use WeakReference patterns
Dispose IDisposable — always use using
Async all the way — don’t mix sync and async without .GetAwaiter().GetResult()
Lock narrow — hold locks for the minimum time possible
Test pure functions — separate logic from I/O, test logic in isolation
Let the compiler help — enable nullable, use readonly, use const — the compiler is your ally

Page 77 of 77

You might also like