Skip to main content

C#

Welcome to C#

C# is a modern, object-oriented programming language developed by Microsoft. It combines the power of C++ with the simplicity of Visual Basic and is part of the .NET ecosystem. C# is designed for building a variety of applications, from web services to desktop applications and games.

Known for its type safety, garbage collection, and rich standard library, C# enables developers to write robust and maintainable code. With features like LINQ, async/await, and extensive framework support, C# is a versatile language used for Windows applications, web development with ASP.NET, game development with Unity, and cross-platform mobile apps with Xamarin.

Introduction to C#

How to set up a C# development environment and write your first "Hello, World!" program.

C# is one of the most popular programming languages, known for its simplicity and powerful features. Setting up a C# development environment is straightforward with modern tools.

Step 1: Install .NET SDK

  1. Windows
    Download and install the .NET SDK from the official Microsoft website. Alternatively, install Visual Studio which includes the .NET SDK.

  2. macOS
    Download the .NET SDK for macOS from the official Microsoft website or use Homebrew: brew install --cask dotnet-sdk

  3. Linux
    Use your distribution's package manager. For Ubuntu/Debian:

    wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
    sudo dpkg -i packages-microsoft-prod.deb
    sudo apt-get update
    sudo apt-get install -y dotnet-sdk-6.0

Step 2: Verify Installation

Open a terminal or command prompt and type:

dotnet --version

This should display the installed .NET version.

Step 3: Write and Run Your First C# Program

  1. Create a new console application:

    dotnet new console -o HelloWorld
    cd HelloWorld
  2. Open the Program.cs file and examine its content:

    // See https://aka.ms/new-console-template for more information
    Console.WriteLine("Hello, World!");
  3. Run the program:

    dotnet run

    You should see the output: Hello, World!

Congratulations! You have successfully set up a C# environment and run your first program. 🎉

C# Syntax Basics

C# syntax is similar to other C-style languages but with modern features and strong typing. Understanding the basic syntax is crucial for writing correct C# programs.

1. Basic Structure of a C# Program

Every C# program has a specific structure:

using System;  // Using directive for namespaces

namespace HelloWorld  // Namespace declaration
{
    class Program     // Class declaration
    {
        static void Main(string[] args)  // Main method - program entry point
        {
            // Program statements go here
            Console.WriteLine("Hello, World!");
        }
    }
}

2. Semicolons and Braces

C# uses semicolons to terminate statements and braces {} to define code blocks:

int x = 5;        // Statement ends with semicolon

if (x > 3)        // Braces define the if block
{
    Console.WriteLine("x is greater than 3");
}                 // Closing brace

3. Comments

C# supports single-line and multi-line comments:

// This is a single-line comment

/*
 This is a multi-line comment
 It can span multiple lines
*/

/// 
/// This is an XML documentation comment
/// 

4. Case Sensitivity

C# is case-sensitive, meaning it distinguishes between uppercase and lowercase letters:

int myVar = 5;    // Different from myvar
int MyVar = 10;   // Different from myVar

Console.WriteLine(myVar);    // Outputs: 5
Console.WriteLine(MyVar);    // Outputs: 10

5. Variables and Strong Typing

C# is strongly typed, meaning you must declare the type of a variable explicitly (or use var for type inference):

int x = 10;              // Integer
float y = 3.14f;         // Floating-point number
char z = 'A';            // Character
string name = "C#";      // String
bool isActive = true;    // Boolean

var inferred = "Hello";  // Type inferred as string
var number = 42;         // Type inferred as int

Console.WriteLine(x.GetType().Name);  // Outputs: Int32

Conclusion

Understanding C# syntax is essential for writing correct and efficient programs. Key takeaways include:

  • C# programs start execution from the Main() method
  • Statements end with semicolons
  • Code blocks are defined with braces {}
  • C# is case-sensitive
  • C# uses strong typing - variable types must be declared or inferred

Output with Console.WriteLine

The Console.WriteLine method in C# is used to display output on the console. It is part of the System namespace and is one of the most commonly used features for basic output and debugging.

1. Basic Console.WriteLine Usage

The simplest way to use Console.WriteLine is with a string or value:

using System;

class Program
{
    static void Main()
    {
        Console.WriteLine("Hello, World!");  // Outputs: Hello, World!
        Console.WriteLine(42);               // Outputs: 42
    }
}

2. Outputting Multiple Values

You can use string interpolation or concatenation to output multiple values:

string firstName = "John";
string lastName = "Doe";
int age = 25;

// String concatenation
Console.WriteLine("Name: " + firstName + " " + lastName + ", Age: " + age);

// String interpolation (C# 6.0+)
Console.WriteLine($"Name: {firstName} {lastName}, Age: {age}");

// Composite formatting
Console.WriteLine("Name: {0} {1}, Age: {2}", firstName, lastName, age);

3. Using Console.Write vs Console.WriteLine

Console.Write outputs without a newline, while Console.WriteLine adds a newline:

Console.Write("First ");
Console.Write("Second ");
Console.WriteLine("Third");
Console.WriteLine("New line");

Output:

First Second Third
New line

4. Formatting Output

C# provides various ways to format output:

double price = 19.99;
DateTime now = DateTime.Now;

// Number formatting
Console.WriteLine($"Price: {price:C}");        // Currency: $19.99
Console.WriteLine($"Price: {price:F2}");       // Fixed point: 19.99
Console.WriteLine($"Percent: {0.25:P}");       // Percent: 25.00%

// Date formatting
Console.WriteLine($"Short date: {now:d}");     // Short date: 6/15/2023
Console.WriteLine($"Long date: {now:D}");      // Long date: Thursday, June 15, 2023
Console.WriteLine($"Time: {now:t}");           // Short time: 1:45 PM

// Alignment and spacing
Console.WriteLine($"{"Name",-10} {"Age",5}");
Console.WriteLine($"{"John",-10} {25,5}");
Console.WriteLine($"{"Alice",-10} {30,5}");

5. Console Color and Background

// Change console colors
Console.ForegroundColor = ConsoleColor.Green;
Console.BackgroundColor = ConsoleColor.Black;
Console.WriteLine("This is green text on black background");

// Reset to default
Console.ResetColor();

Conclusion

The Console.WriteLine method is a fundamental tool for output in C#. Key points:

  • Use Console.WriteLine for output with newline
  • Use Console.Write for output without newline
  • String interpolation provides clean syntax for embedding values
  • Format specifiers enable professional-looking output

Arithmetic Operators in C#

C# provides standard arithmetic operators for mathematical calculations. These operators work with numeric data types like integers and floating-point numbers.

Basic Arithmetic Operators

using System;

class Program
{
    static void Main()
    {
        int a = 15, b = 4;
        
        Console.WriteLine($"a + b = {a + b}");  // Addition: 19
        Console.WriteLine($"a - b = {a - b}");  // Subtraction: 11
        Console.WriteLine($"a * b = {a * b}");  // Multiplication: 60
        Console.WriteLine($"a / b = {a / b}");  // Division: 3 (integer division)
        Console.WriteLine($"a % b = {a % b}");  // Modulus: 3
        
        float x = 15.0f, y = 4.0f;
        Console.WriteLine($"x / y = {x / y}");  // Division: 3.75 (float division)
    }
}

Increment and Decrement Operators

int count = 5;
Console.WriteLine($"count = {count}");         // 5
Console.WriteLine($"++count = {++count}");     // 6 (pre-increment)
Console.WriteLine($"count++ = {count++}");     // 6 (post-increment)
Console.WriteLine($"count = {count}");         // 7
Console.WriteLine($"--count = {--count}");     // 6 (pre-decrement)

Common Pitfalls

  • Integer division truncates the fractional part: 5 / 2 equals 2, not 2.5
  • Modulus operator % works with both integer and floating-point types in C#
  • Division by zero causes DivideByZeroException

Comparison Operators in C#

Comparison operators compare two values and return a boolean result (true or false). These are essential for conditional statements and loops.

Comparison Operators

using System;

class Program
{
    static void Main()
    {
        int x = 7, y = 10;
        
        Console.WriteLine($"x == y: {x == y}");  // Equal to: false
        Console.WriteLine($"x != y: {x != y}");  // Not equal to: true
        Console.WriteLine($"x > y: {x > y}");    // Greater than: false
        Console.WriteLine($"x < y: {x < y}");    // Less than: true
        Console.WriteLine($"x >= 7: {x >= 7}");  // Greater than or equal: true
        Console.WriteLine($"y <= 7: {y <= 7}");  // Less than or equal: false
    }
}

Using Comparisons in Conditions

int age = 18;
if (age >= 18)
{
    Console.WriteLine("You are an adult");
}
else
{
    Console.WriteLine("You are a minor");
}

Common Pitfalls

  • Confusing assignment = with equality ==
  • Comparing floating-point numbers for exact equality can be problematic due to precision issues
  • Using = instead of == in conditions is a compile-time error in C#

Logical Operators in C#

Logical operators combine boolean expressions and return true or false. C# provides three logical operators: && (AND), || (OR), and ! (NOT).

Logical Operators

using System;

class Program
{
    static void Main()
    {
        bool isSunny = true;
        bool isWarm = false;
        
        Console.WriteLine($"isSunny && isWarm: {isSunny && isWarm}");  // AND: false
        Console.WriteLine($"isSunny || isWarm: {isSunny || isWarm}");  // OR: true
        Console.WriteLine($"!isWarm: {!isWarm}");                      // NOT: true
        
        // Complex conditions
        int age = 25;
        bool hasLicense = true;
        
        if (age >= 18 && hasLicense)
        {
            Console.WriteLine("You can drive");
        }
    }
}

Short-Circuit Evaluation

// In AND (&&), if first condition is false, second isn't evaluated
// In OR (||), if first condition is true, second isn't evaluated

int x = 5;
if (x != 0 && 10/x > 1)  // Safe because x != 0 is checked first
{
    Console.WriteLine("Condition satisfied");
}

Common Pitfalls

  • Using bitwise operators (&, |) instead of logical operators (&&, ||)
  • Forgetting that logical operators have lower precedence than comparison operators
  • Not understanding short-circuit evaluation behavior

Bitwise Operators in C#

Bitwise operators perform operations on individual bits of integer types. They are used in low-level programming, flags, and performance optimization.

Bitwise Operators

using System;

class Program
{
    static void Main()
    {
        uint a = 6;   // binary: 0110
        uint b = 3;   // binary: 0011
        
        Console.WriteLine($"a = {Convert.ToString(a, 2).PadLeft(4, '0')} ({a})");
        Console.WriteLine($"b = {Convert.ToString(b, 2).PadLeft(4, '0')} ({b})");
        
        Console.WriteLine($"a & b = {Convert.ToString(a & b, 2).PadLeft(4, '0')} ({a & b})");  // AND: 0010 (2)
        Console.WriteLine($"a | b = {Convert.ToString(a | b, 2).PadLeft(4, '0')} ({a | b})");  // OR: 0111 (7)
        Console.WriteLine($"a ^ b = {Convert.ToString(a ^ b, 2).PadLeft(4, '0')} ({a ^ b})");  // XOR: 0101 (5)
        Console.WriteLine($"~a = {Convert.ToString((uint)~a, 2).Substring(28)} ({(uint)~a})"); // NOT: 1001 (9)
        Console.WriteLine($"a << 1 = {Convert.ToString(a << 1, 2).PadLeft(4, '0')} ({a << 1})"); // Left shift: 1100 (12)
        Console.WriteLine($"b >> 1 = {Convert.ToString(b >> 1, 2).PadLeft(4, '0')} ({b >> 1})"); // Right shift: 0001 (1)
    }
}

Practical Applications

// Setting flags with [Flags] attribute
[Flags]
public enum Permissions
{
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
    All = Read | Write | Execute
}

Permissions userPermissions = Permissions.Read | Permissions.Write;

// Check if specific permission is set
if ((userPermissions & Permissions.Write) == Permissions.Write)
{
    Console.WriteLine("User has write permission");
}

// Adding permissions
userPermissions |= Permissions.Execute;

// Removing permissions
userPermissions &= ~Permissions.Write;

Common Pitfalls

  • Confusing bitwise AND & with logical AND &&
  • Right-shifting signed integers preserves the sign bit
  • Shifting beyond the bit width causes undefined behavior for some types

Assignment Operators in C#

Assignment operators assign values to variables. C# provides compound assignment operators that combine assignment with arithmetic or bitwise operations.

Assignment Operators

using System;

class Program
{
    static void Main()
    {
        int x = 10;        // Simple assignment
        Console.WriteLine($"x = {x}");
        
        x += 5;            // Equivalent to x = x + 5
        Console.WriteLine($"x += 5: {x}");  // 15
        
        x -= 3;            // Equivalent to x = x - 3
        Console.WriteLine($"x -= 3: {x}");  // 12
        
        x *= 2;            // Equivalent to x = x * 2
        Console.WriteLine($"x *= 2: {x}");  // 24
        
        x /= 4;            // Equivalent to x = x / 4
        Console.WriteLine($"x /= 4: {x}");  // 6
        
        x %= 4;            // Equivalent to x = x % 4
        Console.WriteLine($"x %= 4: {x}");  // 2
        
        // Bitwise assignment operators
        uint y = 5;
        y &= 3;            // AND assignment: y = y & 3
        y |= 8;            // OR assignment: y = y | 8
        y ^= 4;            // XOR assignment: y = y ^ 4
        
        Console.WriteLine($"Final y: {y}");
    }
}

Multiple Assignment

int a, b, c;
a = b = c = 10;  // All variables get value 10

// Compound assignment in loops
int sum = 0;
for (int i = 1; i <= 5; i++)
{
    sum += i;  // Add i to sum
}
Console.WriteLine($"Sum: {sum}");  // 15

Common Pitfalls

  • Assignment has right-to-left associativity: a = b = c means a = (b = c)
  • Compound assignment operators have lower precedence than most other operators
  • Using assignment instead of comparison in conditions is a compile error

Integer Data Types in C#

C# provides several integer types with different sizes and ranges. Choosing the right integer type depends on the required range and memory constraints.

Basic Integer Types

using System;

class Program
{
    static void Main()
    {
        // Signed integers (can represent negative numbers)
        sbyte sb = 100;           // 1 byte
        short s = 100;            // 2 bytes
        int i = 100000;           // 4 bytes
        long l = 100000L;         // 8 bytes
        
        // Unsigned integers (only non-negative numbers)
        byte b = 100;
        ushort us = 100;
        uint ui = 100000U;
        ulong ul = 100000UL;
        
        Console.WriteLine($"sbyte range: {sbyte.MinValue} to {sbyte.MaxValue}");
        Console.WriteLine($"int range: {int.MinValue} to {int.MaxValue}");
        Console.WriteLine($"uint range: {uint.MinValue} to {uint.MaxValue}");
        
        // Check type sizes
        Console.WriteLine($"Size of int: {sizeof(int)} bytes");
        Console.WriteLine($"Size of long: {sizeof(long)} bytes");
    }
}

Integer Operations

int a = 10, b = 3;
Console.WriteLine($"a + b = {a + b}");  // 13
Console.WriteLine($"a - b = {a - b}");  // 7
Console.WriteLine($"a * b = {a * b}");  // 30
Console.WriteLine($"a / b = {a / b}");  // 3 (integer division)
Console.WriteLine($"a % b = {a % b}");  // 1 (modulus)

Type Conversion

// Implicit conversion (smaller to larger)
int numInt = 100;
long numLong = numInt;  // Implicit conversion

// Explicit conversion (casting)
double numDouble = 3.14;
int numInt2 = (int)numDouble;  // Explicit conversion - truncates to 3

// String to integer
string str = "123";
int parsed = int.Parse(str);        // Throws exception if invalid
int tryParsed;
bool success = int.TryParse(str, out tryParsed);  // Safe parsing

// Convert class
int converted = Convert.ToInt32("456");

Common Pitfalls

  • Integer overflow when result exceeds type's maximum value
  • Integer division truncates fractional parts
  • Mixing signed and unsigned types can cause unexpected behavior
  • int.Parse throws exception on invalid input

Floating-Point Data Types in C#

Floating-point types represent real numbers with fractional parts. C# provides three floating-point types with different precision levels.

Floating-Point Types

using System;

class Program
{
    static void Main()
    {
        float f = 3.14159f;           // Single precision (4 bytes)
        double d = 3.14159265358979;  // Double precision (8 bytes)
        decimal dec = 3.141592653589793238M; // Decimal precision (16 bytes)
        
        Console.WriteLine($"float: {f}");
        Console.WriteLine($"double: {d}");
        Console.WriteLine($"decimal: {dec}");
        
        // Special values
        Console.WriteLine($"Positive infinity: {double.PositiveInfinity}");
        Console.WriteLine($"Negative infinity: {double.NegativeInfinity}");
        Console.WriteLine($"NaN: {double.NaN}");
        
        // Checking special values
        Console.WriteLine($"Is NaN: {double.IsNaN(double.NaN)}");
        Console.WriteLine($"Is Infinity: {double.IsInfinity(double.PositiveInfinity)}");
    }
}

Floating-Point Operations

double a = 2.5, b = 1.5;
Console.WriteLine($"a + b = {a + b}");  // 4.0
Console.WriteLine($"a - b = {a - b}");  // 1.0
Console.WriteLine($"a * b = {a * b}");  // 3.75
Console.WriteLine($"a / b = {a / b}");  // 1.66667

// Mathematical functions
Console.WriteLine($"sqrt(a) = {Math.Sqrt(a)}");
Console.WriteLine($"pow(a, b) = {Math.Pow(a, b)}");
Console.WriteLine($"sin(pi/2) = {Math.Sin(Math.PI/2)}");

Type Conversion

// String to floating-point
string str = "3.14159";
double pi = double.Parse(str);
bool success = double.TryParse(str, out double result);

// Floating-point to integer (truncation)
double d = 3.99;
int i = (int)d;  // i becomes 3

// Integer to floating-point
int x = 5;
double y = x;  // y becomes 5.0 (implicit conversion)

// Decimal for financial calculations
decimal price = 19.99M;
decimal quantity = 2.5M;
decimal total = price * quantity;  // Precise decimal arithmetic

Common Pitfalls

  • Floating-point numbers have limited precision and rounding errors
  • Comparing floating-point numbers for exact equality is often problematic
  • Operations with very large or very small numbers can cause overflow/underflow
  • Forgetting the 'f' suffix for float literals or 'm' for decimal

Strings in C#

C# provides the string class for working with text. Strings are immutable sequences of characters and offer many convenient operations for text manipulation.

Creating and Using Strings

using System;

class Program
{
    static void Main()
    {
        // Different ways to create strings
        string s1 = "Hello";
        string s2 = new string('A', 5);  // "AAAAA"
        string s3 = string.Concat(s1, " ", "World");
        string s4 = $"{s1} World";  // String interpolation
        
        Console.WriteLine($"s1: {s1}");
        Console.WriteLine($"s2: {s2}");
        Console.WriteLine($"s3: {s3}");
        Console.WriteLine($"s4: {s4}");
        
        // Accessing characters
        Console.WriteLine($"First character: {s1[0]}");  // 'H'
        
        // String properties
        Console.WriteLine($"Length of s1: {s1.Length}");
        Console.WriteLine($"Is s1 empty? {string.IsNullOrEmpty(s1)}");
        
        // Verbatim strings (for paths, etc.)
        string path = @"C:\Users\John\Documents";
        string multiLine = @"This is a
        multi-line
        string";
        
        Console.WriteLine($"Path: {path}");
        Console.WriteLine($"Multi-line: {multiLine}");
    }
}

String Comparison and Searching

string str1 = "apple", str2 = "banana";
    
// Comparison
if (str1 == str2)
{
    Console.WriteLine("Strings are equal");
}
else if (string.Compare(str1, str2) < 0)
{
    Console.WriteLine($"{str1} comes before {str2}");
}
else
{
    Console.WriteLine($"{str1} comes after {str2}");
}

// Searching
string text = "Hello World";
bool contains = text.Contains("World");
int index = text.IndexOf("World");
int lastIndex = text.LastIndexOf('o');

Console.WriteLine($"Contains 'World': {contains}");
Console.WriteLine($"Index of 'World': {index}");
Console.WriteLine($"Last index of 'o': {lastIndex}");

String Modification

string original = "   Hello World   ";
    
// Trimming
string trimmed = original.Trim();
string startTrimmed = original.TrimStart();
string endTrimmed = original.TrimEnd();
    
// Case conversion
string upper = original.ToUpper();
string lower = original.ToLower();
    
// Replacement
string replaced = original.Replace("World", "C#");
    
// Substring
string sub = original.Substring(3, 5);  // "Hello"
    
// Padding
string padded = original.PadLeft(20, '-');
    
Console.WriteLine($"Original: '{original}'");
Console.WriteLine($"Trimmed: '{trimmed}'");
Console.WriteLine($"Upper: '{upper}'");
Console.WriteLine($"Replaced: '{replaced}'");
Console.WriteLine($"Substring: '{sub}'");
Console.WriteLine($"Padded: '{padded}'");

Common Pitfalls

  • Strings are immutable - modification operations return new strings
  • Accessing characters beyond string length throws IndexOutOfRangeException
  • Use StringBuilder for frequent string modifications
  • Use string.IsNullOrEmpty() for null/empty checks

String Functions in C#

The C# string class provides many useful static and instance methods for string manipulation, including splitting, joining, formatting, and validation.

Splitting and Joining Strings

using System;

class Program
{
    static void Main()
    {
        // Splitting strings
        string data = "apple,banana,cherry,date";
        string[] fruits = data.Split(',');
        
        Console.WriteLine("Split fruits:");
        foreach (string fruit in fruits)
        {
            Console.WriteLine($"  - {fruit}");
        }
        
        // Splitting with options
        string textWithSpaces = "  apple  banana  cherry  ";
        string[] cleanFruits = textWithSpaces.Split(new char[] { ' ' }, 
            StringSplitOptions.RemoveEmptyEntries);
        
        // Joining strings
        string joined = string.Join(" - ", fruits);
        Console.WriteLine($"Joined: {joined}");
        
        // Complex splitting
        string sentence = "Hello, World! How are you today?";
        char[] delimiters = new char[] { ' ', ',', '!', '?' };
        string[] words = sentence.Split(delimiters, 
            StringSplitOptions.RemoveEmptyEntries);
        
        Console.WriteLine("Words in sentence:");
        foreach (string word in words)
        {
            Console.WriteLine($"  - {word}");
        }
    }
}

String Validation and Testing

// Null and empty checks
string test1 = null;
string test2 = "";
string test3 = "   ";
string test4 = "Hello";

Console.WriteLine($"test1 is null or empty: {string.IsNullOrEmpty(test1)}");
Console.WriteLine($"test2 is null or empty: {string.IsNullOrEmpty(test2)}");
Console.WriteLine($"test3 is null or whitespace: {string.IsNullOrWhiteSpace(test3)}");
Console.WriteLine($"test4 is null or empty: {string.IsNullOrEmpty(test4)}");

// Character testing
string mixed = "Hello123";
Console.WriteLine($"Is 'Hello123' all letters? {IsAllLetters(mixed)}");

bool IsAllLetters(string s)
{
    foreach (char c in s)
    {
        if (!char.IsLetter(c))
            return false;
    }
    return true;
}

// StartsWith and EndsWith
string filename = "document.pdf";
Console.WriteLine($"Is PDF: {filename.EndsWith(".pdf")}");
Console.WriteLine($"Starts with 'doc': {filename.StartsWith("doc")}");

StringBuilder for Efficient Manipulation

using System.Text;

// Use StringBuilder for frequent modifications
StringBuilder sb = new StringBuilder();

// Append operations
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
sb.AppendLine("!");  // Adds line terminator
sb.AppendFormat("The time is {0:HH:mm}", DateTime.Now);

// Insert and remove
sb.Insert(5, " Beautiful");  // "Hello Beautiful World!"
sb.Remove(5, 10);           // Back to "Hello World!"

// Convert to string
string result = sb.ToString();
Console.WriteLine(result);

// Performance comparison
DateTime start = DateTime.Now;
string slow = "";
for (int i = 0; i < 10000; i++)
{
    slow += i.ToString();  // Creates new string each time
}
TimeSpan slowTime = DateTime.Now - start;

start = DateTime.Now;
StringBuilder fast = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
    fast.Append(i);
}
string fastResult = fast.ToString();
TimeSpan fastTime = DateTime.Now - start;

Console.WriteLine($"String concatenation: {slowTime.TotalMilliseconds}ms");
Console.WriteLine($"StringBuilder: {fastTime.TotalMilliseconds}ms");

Common Pitfalls

  • Using + concatenation in loops - use StringBuilder instead
  • Forgetting that strings are case-sensitive by default
  • Not using StringComparison for culture-aware comparisons
  • Memory overhead from many temporary string objects

String Formatting in C#

C# provides several powerful ways to format strings, including composite formatting, string interpolation, and custom formatters.

Composite Formatting

using System;

class Program
{
    static void Main()
    {
        string name = "Alice";
        int age = 25;
        double salary = 55000.50;
        DateTime birthDate = new DateTime(1998, 6, 15);
        
        // Composite formatting with {0}, {1}, etc.
        string message1 = string.Format("Name: {0}, Age: {1}, Salary: {2:C}", 
            name, age, salary);
        Console.WriteLine(message1);
        
        // Format specifiers
        string message2 = string.Format("Salary: {0:C2}, Age: {1:D3}, Weight: {2:F1}kg", 
            salary, age, 68.5);
        Console.WriteLine(message2);
        
        // Date formatting
        string dateMessage = string.Format("Born: {0:yyyy-MM-dd} ({0:dddd})", birthDate);
        Console.WriteLine(dateMessage);
        
        // Alignment and spacing
        string table = string.Format("{0,-10} {1,5} {2,10:C}\n{3,-10} {4,5} {5,10:C}",
            "Alice", 25, 55000.50, "Bob", 30, 65000.75);
        Console.WriteLine(table);
    }
}

String Interpolation (C# 6.0+)

string product = "Laptop";
decimal price = 1299.99m;
int quantity = 3;
DateTime purchaseDate = DateTime.Now;

// Basic interpolation
string receipt = $"Product: {product}, Price: {price:C}, Quantity: {quantity}";
Console.WriteLine(receipt);

// Expressions in interpolations
string total = $"Total: {price * quantity:C}";
Console.WriteLine(total);

// Format specifiers in interpolation
string detailed = $"""
    Purchase Date: {purchaseDate:yyyy-MM-dd HH:mm}
    Product: {product,-15} 
    Price: {price,10:C2}
    Quantity: {quantity,8}
    Total: {price * quantity,10:C2}
    """;
Console.WriteLine(detailed);

// Conditional formatting
string status = "active";
string userMessage = $"User is {(status == "active" ? "online" : "offline")}";
Console.WriteLine(userMessage);

Custom String Formatting

// Custom formatter
public class PersonFormatter : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        return formatType == typeof(ICustomFormatter) ? this : null;
    }
    
    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        if (arg is Person person)
        {
            return format?.ToUpper() switch
            {
                "F" => $"{person.FirstName} {person.LastName}",
                "L" => $"{person.LastName}, {person.FirstName}",
                "I" => $"{person.FirstName[0]}. {person.LastName}",
                _ => person.ToString()
            };
        }
        return arg?.ToString() ?? string.Empty;
    }
}

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    
    public Person(string first, string last)
    {
        FirstName = first;
        LastName = last;
    }
}

// Using custom formatter
Person person = new Person("John", "Doe");
string fullName = string.Format(new PersonFormatter(), "{0:F}", person);
string lastFirst = string.Format(new PersonFormatter(), "{0:L}", person);
string initial = string.Format(new PersonFormatter(), "{0:I}", person);

Console.WriteLine($"Full: {fullName}");
Console.WriteLine($"Last, First: {lastFirst}");
Console.WriteLine($"Initial: {initial}");

Number and Date Formatting

// Number formatting
double number = 1234.5678;
Console.WriteLine($"Default: {number}");
Console.WriteLine($"Currency: {number:C}");
Console.WriteLine($"Fixed 2: {number:F2}");
Console.WriteLine($"Exponential: {number:E}");
Console.WriteLine($"Percent: {0.25:P}");
Console.WriteLine($"Hex: {255:X}");

// Custom numeric formats
Console.WriteLine($"Phone: {5551234567:(###) ###-####}");
Console.WriteLine($"Custom: {123.456:###.##}");

// Date and time formatting
DateTime now = DateTime.Now;
Console.WriteLine($"Short date: {now:d}");
Console.WriteLine($"Long date: {now:D}");
Console.WriteLine($"Full date/time: {now:F}");
Console.WriteLine($"Month/year: {now:Y}");
Console.WriteLine($"Custom: {now:yyyy-MM-dd HH:mm:ss}");

// Culture-specific formatting
System.Globalization.CultureInfo french = new System.Globalization.CultureInfo("fr-FR");
Console.WriteLine($"French currency: {55000.50.ToString("C", french)}");

Common Pitfalls

  • Forgetting that format strings are case-sensitive
  • Not handling null values in format operations
  • Using wrong culture info for international applications
  • Complex format strings can be hard to read and maintain

Arrays in C#

Arrays are collections of elements of the same type stored in contiguous memory. C# supports single-dimensional, multi-dimensional, and jagged arrays.

Single-Dimensional Arrays

using System;

class Program
{
    static void Main()
    {
        // Declaration and initialization
        int[] numbers = { 1, 2, 3, 4, 5 };
        string[] names = new string[3] { "Alice", "Bob", "Charlie" };
        double[] prices = new double[4];  // Initialized to zeros
        
        // Accessing elements
        Console.WriteLine($"First element: {numbers[0]}");
        Console.WriteLine($"Last element: {numbers[numbers.Length - 1]}");
        
        // Modifying elements
        numbers[2] = 100;
        prices[0] = 19.99;
        
        // Array properties
        Console.WriteLine($"Array length: {numbers.Length}");
        Console.WriteLine($"Array rank (dimensions): {numbers.Rank}");
        
        // Iterating through array
        Console.WriteLine("Numbers array:");
        for (int i = 0; i < numbers.Length; i++)
        {
            Console.WriteLine($"  numbers[{i}] = {numbers[i]}");
        }
        
        // foreach loop
        Console.WriteLine("Names array:");
        foreach (string name in names)
        {
            Console.WriteLine($"  - {name}");
        }
    }
}

Multi-dimensional Arrays

// 2D array (matrix)
int[,] matrix = new int[3, 3]
{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// Accessing elements
Console.WriteLine($"Element at [1,2]: {matrix[1, 2]}");  // 6

// Getting dimensions
int rows = matrix.GetLength(0);
int cols = matrix.GetLength(1);
Console.WriteLine($"Matrix dimensions: {rows}x{cols}");

// Nested loops for 2D array
for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        Console.Write($"{matrix[i, j]} ");
    }
    Console.WriteLine();
}

// 3D array
int[,,] cube = new int[2, 2, 2]
{
    {
        {1, 2},
        {3, 4}
    },
    {
        {5, 6},
        {7, 8}
    }
};

Jagged Arrays

// Jagged array (array of arrays)
int[][] jagged = new int[3][];
jagged[0] = new int[] { 1, 2, 3 };
jagged[1] = new int[] { 4, 5 };
jagged[2] = new int[] { 6, 7, 8, 9 };

// Accessing elements
Console.WriteLine($"jagged[0][1]: {jagged[0][1]}");  // 2

// Iterating through jagged array
for (int i = 0; i < jagged.Length; i++)
{
    Console.Write($"Row {i}: ");
    for (int j = 0; j < jagged[i].Length; j++)
    {
        Console.Write($"{jagged[i][j]} ");
    }
    Console.WriteLine();
}

// Initialization syntax
int[][] jagged2 = new int[][]
{
    new int[] {1, 2},
    new int[] {3, 4, 5},
    new int[] {6}
};

Common Pitfalls

  • Array indices start at 0, not 1
  • Accessing out-of-bounds indices throws IndexOutOfRangeException
  • Arrays are reference types - assignment copies reference, not data
  • Fixed size - cannot resize after creation

Array Indexing in C#

Array indexing allows access to individual elements in an array using their position. C# uses zero-based indexing and provides bounds checking.

Basic Array Indexing

using System;

class Program
{
    static void Main()
    {
        int[] numbers = { 10, 20, 30, 40, 50 };
        
        // Accessing elements by index
        Console.WriteLine($"numbers[0] = {numbers[0]}");  // First element: 10
        Console.WriteLine($"numbers[2] = {numbers[2]}");  // Third element: 30
        Console.WriteLine($"numbers[4] = {numbers[4]}");  // Last element: 50
        
        // Using variables as indices
        int index = 1;
        Console.WriteLine($"numbers[{index}] = {numbers[index]}");  // 20
        
        // Calculating indices
        int lastIndex = numbers.Length - 1;
        Console.WriteLine($"Last element: {numbers[lastIndex]}");  // 50
        
        // Modifying elements
        numbers[1] = 25;  // Change second element from 20 to 25
        numbers[3] = 45;  // Change fourth element from 40 to 45
        
        // Display modified array
        for (int i = 0; i < numbers.Length; i++)
        {
            Console.WriteLine($"numbers[{i}] = {numbers[i]}");
        }
        
        // Bounds checking
        try
        {
            Console.WriteLine(numbers[10]);  // This will throw exception
        }
        catch (IndexOutOfRangeException ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

Multi-dimensional Array Indexing

// 2D array indexing
int[,] matrix = {
    {1, 2, 3},
    {4, 5, 6}
};

Console.WriteLine($"matrix[0,1] = {matrix[0, 1]}");  // 2
Console.WriteLine($"matrix[1,2] = {matrix[1, 2]}");  // 6

// 3D array indexing
int[,,] cube = new int[2, 2, 2]
{
    {
        {1, 2},
        {3, 4}
    },
    {
        {5, 6},
        {7, 8}
    }
};
Console.WriteLine($"cube[1,0,1] = {cube[1, 0, 1]}");  // 6

// Looping with indices
for (int i = 0; i < matrix.GetLength(0); i++)
{
    for (int j = 0; j < matrix.GetLength(1); j++)
    {
        Console.WriteLine($"matrix[{i},{j}] = {matrix[i, j]}");
    }
}

Jagged Array Indexing

// Jagged array indexing
string[][] teams = new string[3][];
teams[0] = new string[] { "Alice", "Bob" };
teams[1] = new string[] { "Charlie", "David", "Eve" };
teams[2] = new string[] { "Frank" };

// Accessing elements
Console.WriteLine($"teams[0][1] = {teams[0][1]}");  // Bob
Console.WriteLine($"teams[1][2] = {teams[1][2]}");  // Eve

// Safe indexing with bounds checking
int teamIndex = 1;
int memberIndex = 5;

if (teamIndex < teams.Length && memberIndex < teams[teamIndex].Length)
{
    Console.WriteLine(teams[teamIndex][memberIndex]);
}
else
{
    Console.WriteLine("Invalid indices");
}

// Using foreach with jagged arrays
foreach (string[] team in teams)
{
    foreach (string member in team)
    {
        Console.Write($"{member} ");
    }
    Console.WriteLine();
}

Array Index Methods

int[] data = { 10, 20, 30, 20, 40, 50, 20 };
        
// Finding indices
int firstIndex = Array.IndexOf(data, 20);
int lastIndex = Array.LastIndexOf(data, 20);
int indexOf30 = Array.IndexOf(data, 30);

Console.WriteLine($"First index of 20: {firstIndex}");  // 1
Console.WriteLine($"Last index of 20: {lastIndex}");    // 6
Console.WriteLine($"Index of 30: {indexOf30}");         // 2

// Finding index with condition
int firstEven = Array.FindIndex(data, x => x % 2 == 0);
int lastGreaterThan25 = Array.FindLastIndex(data, x => x > 25);

Console.WriteLine($"First even: {firstEven}");              // 0
Console.WriteLine($"Last > 25: {lastGreaterThan25}");       // 6

// Binary search (requires sorted array)
int[] sorted = { 10, 20, 30, 40, 50 };
int index40 = Array.BinarySearch(sorted, 40);
Console.WriteLine($"Binary search for 40: {index40}");  // 3

Common Pitfalls

  • Accessing out-of-bounds indices throws IndexOutOfRangeException
  • Array indices start at 0, not 1
  • Forgetting that multi-dimensional arrays use comma syntax: [i,j]
  • Confusing jagged arrays with multi-dimensional arrays

Array Functions in C#

C# provides the Array class with many static methods for array manipulation, including sorting, searching, copying, and resizing.

Array Sorting and Searching

using System;

class Program
{
    static void Main()
    {
        int[] numbers = { 5, 2, 8, 1, 9, 3, 7, 4, 6 };
        
        // Original array
        Console.WriteLine("Original array:");
        PrintArray(numbers);
        
        // Sort array
        Array.Sort(numbers);
        Console.WriteLine("\nSorted array:");
        PrintArray(numbers);
        
        // Reverse array
        Array.Reverse(numbers);
        Console.WriteLine("\nReversed array:");
        PrintArray(numbers);
        
        // Binary search (requires sorted array)
        Array.Sort(numbers);  // Ensure sorted for binary search
        int index = Array.BinarySearch(numbers, 7);
        if (index >= 0)
        {
            Console.WriteLine($"\nFound 7 at index: {index}");
        }
        
        // Finding elements
        int firstEven = Array.Find(numbers, x => x % 2 == 0);
        int[] allEvens = Array.FindAll(numbers, x => x % 2 == 0);
        
        Console.WriteLine($"First even: {firstEven}");
        Console.WriteLine("All evens:");
        PrintArray(allEvens);
    }
    
    static void PrintArray(int[] arr)
    {
        foreach (int num in arr)
        {
            Console.Write($"{num} ");
        }
        Console.WriteLine();
    }
}

Array Copying and Resizing

int[] original = { 1, 2, 3, 4, 5 };
        
// Copy array - method 1
int[] copy1 = new int[original.Length];
Array.Copy(original, copy1, original.Length);

// Copy array - method 2
int[] copy2 = (int[])original.Clone();

// Copy array - method 3
int[] copy3 = new int[original.Length];
original.CopyTo(copy3, 0);

Console.WriteLine("Original:");
PrintArray(original);
Console.WriteLine("Copy:");
PrintArray(copy1);

// Copy portion of array
int[] partial = new int[3];
Array.Copy(original, 1, partial, 0, 3);  // Copy 3 elements starting from index 1
Console.WriteLine("Partial copy (from index 1):");
PrintArray(partial);

// Resize array
Array.Resize(ref original, 8);
Console.WriteLine("Resized to 8 elements:");
PrintArray(original);

Array.Resize(ref original, 3);
Console.WriteLine("Resized to 3 elements:");
PrintArray(original);

// Clear array elements
Array.Clear(original, 0, original.Length);
Console.WriteLine("After clearing:");
PrintArray(original);

Array Conversion and Manipulation

// ConvertAll - transform array elements
int[] integers = { 1, 2, 3, 4, 5 };
string[] strings = Array.ConvertAll(integers, x => x.ToString());
double[] squares = Array.ConvertAll(integers, x => x * x);

Console.WriteLine("Integers:");
PrintArray(integers);
Console.WriteLine("Strings:");
foreach (string s in strings) Console.Write($"{s} ");
Console.WriteLine("\nSquares:");
PrintArray(squares);

// TrueForAll - test all elements
bool allPositive = Array.TrueForAll(integers, x => x > 0);
bool allEven = Array.TrueForAll(integers, x => x % 2 == 0);

Console.WriteLine($"All positive: {allPositive}");
Console.WriteLine($"All even: {allEven}");

// ForEach - perform action on each element
Console.Write("Squared values: ");
Array.ForEach(integers, x => Console.Write($"{x * x} "));
Console.WriteLine();

// Multi-dimensional array operations
int[,] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// Get length of specific dimension
int rows = matrix.GetLength(0);
int cols = matrix.GetLength(1);
Console.WriteLine($"Matrix dimensions: {rows}x{cols}");

// Copy multi-dimensional array
int[,] matrixCopy = (int[,])matrix.Clone();

Array Utility Methods

// Initialize array with values
int[] arr = new int[5];
Array.Fill(arr, 42);  // Fill with 42
Console.WriteLine("Array filled with 42:");
PrintArray(arr);

// Array constraints and validation
void ProcessArray<T>(T[] array) where T : IComparable
{
    if (array == null)
        throw new ArgumentNullException(nameof(array));
    
    if (array.Length == 0)
        throw new ArgumentException("Array cannot be empty", nameof(array));
    
    Array.Sort(array);
    Console.WriteLine("Sorted array:");
    foreach (T item in array)
    {
        Console.Write($"{item} ");
    }
    Console.WriteLine();
}

// Using the generic method
int[] testArray = { 3, 1, 4, 1, 5 };
ProcessArray(testArray);

string[] words = { "banana", "apple", "cherry" };
ProcessArray(words);

// Array existence checks
bool exists = Array.Exists(testArray, x => x > 3);
int count = Array.FindAll(testArray, x => x > 2).Length;

Console.WriteLine($"Exists > 3: {exists}");
Console.WriteLine($"Count > 2: {count}");

Common Pitfalls

  • Array.Resize creates a new array - original reference becomes invalid
  • Binary search requires sorted arrays
  • Clone() creates shallow copies for reference types
  • Forgetting that arrays are zero-indexed when using copy ranges

Lists in C#

Lists are dynamic arrays that can grow and shrink in size. They are part of the System.Collections.Generic namespace and provide many convenient operations.

Creating and Using Lists

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // Different ways to create lists
        List<int> numbers = new List<int>();                    // Empty list
        List<int> primes = new List<int> { 2, 3, 5, 7, 11 };   // Initialized
        List<string> names = new List<string>(3) { "Alice", "Bob", "Charlie" };
        
        // Adding elements
        numbers.Add(10);
        numbers.Add(20);
        numbers.Add(30);
        numbers.AddRange(new int[] { 40, 50, 60 });  // Add multiple
        
        // Accessing elements
        Console.WriteLine($"First: {numbers[0]}");
        Console.WriteLine($"Second: {numbers[1]}");
        
        // List properties
        Console.WriteLine($"Count: {numbers.Count}");
        Console.WriteLine($"Capacity: {numbers.Capacity}");
        Console.WriteLine($"Empty? {numbers.Count == 0}");
        
        // Iterating through list
        Console.WriteLine("Numbers list:");
        for (int i = 0; i < numbers.Count; i++)
        {
            Console.WriteLine($"  numbers[{i}] = {numbers[i]}");
        }
        
        // foreach loop
        Console.WriteLine("Using foreach:");
        foreach (int num in numbers)
        {
            Console.Write($"{num} ");
        }
        Console.WriteLine();
    }
}

List Operations

List<string> fruits = new List<string> 
    { "apple", "banana", "cherry", "date", "elderberry" };
    
// Inserting elements
fruits.Insert(2, "blueberry");        // Insert at position 2
fruits.InsertRange(0, new string[] { "apricot", "avocado" });  // Insert at beginning

// Removing elements
fruits.Remove("banana");              // Remove by value
fruits.RemoveAt(0);                   // Remove by index
fruits.RemoveRange(2, 2);             // Remove 2 elements starting from index 2
fruits.RemoveAll(f => f.Length > 6);  // Remove all with length > 6

// Finding elements
string firstWithA = fruits.Find(f => f.StartsWith("a"));
List<string> allWithA = fruits.FindAll(f => f.StartsWith("a"));
int indexOfCherry = fruits.IndexOf("cherry");
int lastIndex = fruits.LastIndexOf("date");

// Checking conditions
bool hasApple = fruits.Contains("apple");
bool allShort = fruits.TrueForAll(f => f.Length < 10);
bool anyLong = fruits.Exists(f => f.Length > 8);

Console.WriteLine("Final fruits list:");
foreach (string fruit in fruits)
{
    Console.WriteLine($"  - {fruit}");
}

List Performance and Capacity

// Capacity management
List<int> numbers = new List<int>();
Console.WriteLine($"Initial capacity: {numbers.Capacity}");

// Adding elements - capacity doubles when needed
for (int i = 0; i < 20; i++)
{
    numbers.Add(i);
    Console.WriteLine($"Count: {numbers.Count}, Capacity: {numbers.Capacity}");
}

// Pre-allocate capacity for better performance
List<int> optimized = new List<int>(1000);  // Pre-allocate capacity
for (int i = 0; i < 1000; i++)
{
    optimized.Add(i);
}

// Trim excess capacity
optimized.TrimExcess();
Console.WriteLine($"After trim - Count: {optimized.Count}, Capacity: {optimized.Capacity}");

// Performance comparison: List vs Array
const int SIZE = 100000;

// Array - fixed size
int[] array = new int[SIZE];
DateTime start = DateTime.Now;
for (int i = 0; i < SIZE; i++)
{
    if (i < array.Length)
        array[i] = i;
}
TimeSpan arrayTime = DateTime.Now - start;

// List - dynamic but slower for large pre-known sizes
List<int> list = new List<int>();
start = DateTime.Now;
for (int i = 0; i < SIZE; i++)
{
    list.Add(i);
}
TimeSpan listTime = DateTime.Now - start;

// List with pre-allocation - much faster
List<int> listOptimized = new List<int>(SIZE);
start = DateTime.Now;
for (int i = 0; i < SIZE; i++)
{
    listOptimized.Add(i);
}
TimeSpan listOptimizedTime = DateTime.Now - start;

Console.WriteLine($"Array time: {arrayTime.TotalMilliseconds}ms");
Console.WriteLine($"List time: {listTime.TotalMilliseconds}ms");
Console.WriteLine($"Optimized list time: {listOptimizedTime.TotalMilliseconds}ms");

Advanced List Features

// List conversion
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
int[] array = numbers.ToArray();                    // Convert to array
List<string> strings = numbers.ConvertAll(x => x.ToString());  // Convert all elements

// List sorting and searching
List<Person> people = new List<Person>
{
    new Person("Alice", 25),
    new Person("Bob", 30),
    new Person("Charlie", 22)
};

// Sort by age
people.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));
// Or using LINQ
var sortedByName = people.OrderBy(p => p.Name).ToList();

// Binary search (requires sorted list)
people.Sort((p1, p2) => p1.Age.CompareTo(p2.Age));
int index = people.BinarySearch(new Person("", 25), 
    Comparer<Person>.Create((p1, p2) => p1.Age.CompareTo(p2.Age)));

// List ranges (C# 8.0+)
var firstThree = numbers.GetRange(0, 3);
numbers.RemoveRange(1, 2);

// List as stack/queue operations
List<int> stackLike = new List<int> { 1, 2, 3 };
stackLike.Add(4);           // Push
int last = stackLike[stackLike.Count - 1];  // Peek
stackLike.RemoveAt(stackLike.Count - 1);    // Pop

List<int> queueLike = new List<int> { 1, 2, 3 };
queueLike.Add(4);           // Enqueue
int first = queueLike[0];   // Peek
queueLike.RemoveAt(0);      // Dequeue

public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    
    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }
}

Common Pitfalls

  • Using [] indexer without checking bounds
  • Not pre-allocating capacity when size is known in advance
  • Modifying list while iterating with foreach
  • Forgetting that Remove only removes first occurrence

Pointers in C#

Pointers in C# provide direct memory access but require unsafe context. They are used for performance-critical code, interop, and low-level operations.

Basic Pointer Operations

using System;

class Program
{
    static unsafe void Main()
    {
        int number = 42;
        int* ptr = &number;  // ptr stores address of number
        
        Console.WriteLine($"Value of number: {number}");
        Console.WriteLine($"Address of number: {(long)ptr:X}");
        Console.WriteLine($"Value at ptr: {*ptr}");  // Dereferencing
        
        // Modifying through pointer
        *ptr = 100;
        Console.WriteLine($"New value of number: {number}");
        
        // Pointer to different types
        double pi = 3.14159;
        double* dptr = π
        Console.WriteLine($"Value at dptr: {*dptr}");
        
        // Pointer arithmetic
        int[] numbers = { 10, 20, 30, 40, 50 };
        fixed (int* arrPtr = numbers)
        {
            int* current = arrPtr;
            Console.WriteLine($"First element: {*current}");        // 10
            current++;  // Move to next int
            Console.WriteLine($"Second element: {*current}");       // 20
            current += 2;  // Move two elements forward
            Console.WriteLine($"Fourth element: {*current}");       // 40
        }
    }
}

Pointers and Arrays

unsafe void PointerArrayOperations()
{
    int[] arr = { 1, 2, 3, 4, 5 };
    
    // fixed statement pins array in memory
    fixed (int* ptr = arr)
    {
        // Access array elements through pointer
        for (int i = 0; i < arr.Length; i++)
        {
            Console.WriteLine($"arr[{i}] = {*(ptr + i)}");
        }
        
        // Modify array through pointer
        *(ptr + 2) = 100;  // Change third element to 100
        Console.WriteLine("After modification:");
        foreach (int num in arr)
        {
            Console.Write($"{num} ");
        }
        Console.WriteLine();
    }
    
    // Multi-dimensional arrays
    int[,] matrix = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
    fixed (int* matrixPtr = matrix)
    {
        // Access elements in row-major order
        for (int i = 0; i < 6; i++)
        {
            Console.Write($"{matrixPtr[i]} ");
        }
        Console.WriteLine();
    }
}

Pointer Safety and Best Practices

unsafe void SafePointerOperations()
{
    // Stack allocation
    int* stackArray = stackalloc int[5];
    for (int i = 0; i < 5; i++)
    {
        stackArray[i] = i * 10;
    }
    
    // Safe access with bounds checking
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine($"stackArray[{i}] = {stackArray[i]}");
    }
    
    // Pointer to struct
    Point point = new Point { X = 10, Y = 20 };
    Point* pointPtr = &point;
    Console.WriteLine($"Point: ({pointPtr->X}, {pointPtr->Y})");
    
    // Sizeof operator
    Console.WriteLine($"Size of int: {sizeof(int)} bytes");
    Console.WriteLine($"Size of Point: {sizeof(Point)} bytes");
    
    // Pointer comparison
    int a = 5, b = 10;
    int* ptrA = &a, ptrB = &b;
    Console.WriteLine($"ptrA == ptrB: {ptrA == ptrB}");
    Console.WriteLine($"ptrA < ptrB: {ptrA < ptrB}");
}

struct Point
{
    public int X;
    public int Y;
}

// Enable unsafe code in project file: <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

Common Pitfalls

  • Dereferencing null or invalid pointers causes crashes
  • Pointer arithmetic can easily go out of bounds
  • Forgetting to use fixed statement for managed objects
  • Memory leaks if unmanaged resources aren't properly freed

Pointer Arithmetic in C#

Pointer arithmetic allows you to perform mathematical operations on pointers to navigate through memory blocks. Operations include addition, subtraction, increment, decrement, and comparison.

Basic Pointer Arithmetic

unsafe void BasicPointerArithmetic()
{
    int[] numbers = { 10, 20, 30, 40, 50 };
    
    fixed (int* ptr = numbers)
    {
        int* current = ptr;
        
        // Pointer addition
        Console.WriteLine($"Base address: {(long)current:X}");
        current = current + 1;  // Move forward by one int (4 bytes)
        Console.WriteLine($"After +1: {(long)current:X}, Value: {*current}");
        
        // Pointer subtraction
        current = current - 1;  // Move back by one int
        Console.WriteLine($"After -1: {(long)current:X}, Value: {*current}");
        
        // Increment and decrement operators
        current++;  // Post-increment
        Console.WriteLine($"After ++: Value: {*current}");
        --current;  // Pre-decrement
        Console.WriteLine($"After --: Value: {*current}");
        
        // Addition with larger offsets
        int* fourthElement = ptr + 3;
        Console.WriteLine($"Fourth element: {*fourthElement}");
    }
}

Type-Specific Arithmetic

unsafe void TypeSpecificArithmetic()
{
    // Different types have different sizes
    Console.WriteLine($"Size of int: {sizeof(int)} bytes");
    Console.WriteLine($"Size of double: {sizeof(double)} bytes");
    Console.WriteLine($"Size of char: {sizeof(char)} bytes");
    
    // Pointer arithmetic is type-aware
    int[] intArray = { 1, 2, 3, 4, 5 };
    double[] doubleArray = { 1.1, 2.2, 3.3, 4.4, 5.5 };
    char[] charArray = { 'A', 'B', 'C', 'D', 'E' };
    
    fixed (int* intPtr = intArray)
    fixed (double* doublePtr = doubleArray)
    fixed (char* charPtr = charArray)
    {
        // Each +1 moves by the size of the type
        Console.WriteLine($"int pointer: {*intPtr} at {(long)intPtr:X}");
        Console.WriteLine($"int pointer + 1: {*(intPtr + 1)} at {(long)(intPtr + 1):X}");
        Console.WriteLine($"Address difference: {(long)(intPtr + 1) - (long)intPtr} bytes");
        
        Console.WriteLine($"double pointer: {*doublePtr} at {(long)doublePtr:X}");
        Console.WriteLine($"double pointer + 1: {*(doublePtr + 1)} at {(long)(doublePtr + 1):X}");
        Console.WriteLine($"Address difference: {(long)(doublePtr + 1) - (long)doublePtr} bytes");
        
        Console.WriteLine($"char pointer: {*charPtr} at {(long)charPtr:X}");
        Console.WriteLine($"char pointer + 1: {*(charPtr + 1)} at {(long)(charPtr + 1):X}");
        Console.WriteLine($"Address difference: {(long)(charPtr + 1) - (long)charPtr} bytes");
    }
}

Pointer Comparison and Differences

unsafe void PointerComparison()
{
    int[] data = { 100, 200, 300, 400, 500 };
    
    fixed (int* start = data)
    {
        int* first = start;
        int* third = start + 2;
        int* fifth = start + 4;
        
        // Pointer comparisons
        Console.WriteLine($"first < third: {first < third}");
        Console.WriteLine($"third > fifth: {third > fifth}");
        Console.WriteLine($"first == start: {first == start}");
        
        // Pointer differences (number of elements between pointers)
        long byteDifference = (long)fifth - (long)first;
        long elementDifference = (fifth - first);
        
        Console.WriteLine($"Byte difference: {byteDifference}");
        Console.WriteLine($"Element difference: {elementDifference}");
        
        // Validating pointer relationships
        Console.WriteLine($"first >= start: {first >= start}");
        Console.WriteLine($"fifth <= start + 4: {fifth <= start + 4}");
        
        // Null pointer comparison
        int* nullPtr = null;
        Console.WriteLine($"first != null: {first != null}");
        Console.WriteLine($"nullPtr == null: {nullPtr == null}");
    }
}

Advanced Arithmetic Operations

unsafe void AdvancedPointerArithmetic()
{
    byte[] buffer = new byte[100];
    
    // Initialize buffer with values
    for (int i = 0; i < buffer.Length; i++)
    {
        buffer[i] = (byte)(i % 256);
    }
    
    fixed (byte* basePtr = buffer)
    {
        // Using pointer arithmetic for buffer manipulation
        byte* current = basePtr;
        
        // Skip header (first 10 bytes)
        current += 10;
        Console.WriteLine($"After header: {*current}");
        
        // Process data in chunks of 4 bytes
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"Chunk {i}: {*current}, {*(current + 1)}, {*(current + 2)}, {*(current + 3)}");
            current += 4;
        }
        
        // Calculate offset from base
        long offset = current - basePtr;
        Console.WriteLine($"Current offset from base: {offset} bytes");
        
        // Reset to base and work backwards from end
        current = basePtr + buffer.Length - 1;
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"From end -{i}: {*current}");
            current--;
        }
    }
}

// Pointer arithmetic with structs
unsafe void StructPointerArithmetic()
{
    Point[] points = new Point[5];
    
    // Initialize points
    for (int i = 0; i < points.Length; i++)
    {
        points[i] = new Point { X = i * 10, Y = i * 20 };
    }
    
    fixed (Point* pointsPtr = points)
    {
        Point* current = pointsPtr;
        
        // Navigate through struct array
        for (int i = 0; i < points.Length; i++)
        {
            Console.WriteLine($"Point {i}: ({current->X}, {current->Y})");
            current++;  // Moves by sizeof(Point) bytes
        }
        
        // Calculate size information
        Console.WriteLine($"Size of Point struct: {sizeof(Point)} bytes");
        Console.WriteLine($"Distance between elements: {(long)(pointsPtr + 1) - (long)pointsPtr} bytes");
    }
}

struct Point
{
    public int X;
    public int Y;
    public byte Flag;
}

Pointer Arithmetic Pitfalls

  • Pointer arithmetic can easily access memory outside allocated bounds
  • Arithmetic on void* pointers is not allowed (size unknown)
  • Pointer differences only make sense within the same memory block
  • Type scaling happens automatically - be aware of the element size
  • Arithmetic on null pointers or invalid pointers causes undefined behavior
  • Pointer comparisons between different arrays are meaningless

Variables in C#

Variables are named storage locations in memory that hold data. C# is a strongly-typed language, meaning variable types must be declared explicitly or inferred by the compiler.

Variable Declaration and Initialization

using System;

class Program
{
    static void Main()
    {
        // Declaration and initialization
        int age = 25;
        double price = 19.99;
        char grade = 'A';
        string name = "Alice";
        bool isActive = true;
        
        // Multiple variables
        int x = 5, y = 10, z = 15;
        
        // Different initialization styles
        int a = 10;     // Copy initialization
        int b = 20;     // Alternative syntax
        var c = 30;     // Type inference (C# 3.0+)
        var d = "Hello"; // Inferred as string
        
        Console.WriteLine($"a: {a}, b: {b}, c: {c}, d: {d}");
        
        // Constants
        const double PI = 3.14159;
        const int MAX_SIZE = 100;
        // PI = 3.14;  // Error: const cannot be modified
        
        // Readonly fields (can be set in constructor)
        readonly string databaseUrl;
        
        Console.WriteLine($"PI: {PI}, MAX_SIZE: {MAX_SIZE}");
    }
}

Variable Scope

int globalVar = 100;  // Field (class-level variable)

class ScopeDemo
{
    static int classLevel = 50;  // Static field
    
    void DemoMethod()
    {
        int localVar = 25;  // Local variable
        Console.WriteLine($"Local: {localVar}, Class: {classLevel}");
        
        {
            int blockVar = 10;  // Block scope
            Console.WriteLine($"Block: {blockVar}");
            // blockVar is only accessible within this block
        }
        // Console.WriteLine(blockVar);  // Error: blockVar not accessible here
    }
    
    static void Main()
    {
        ScopeDemo demo = new ScopeDemo();
        demo.DemoMethod();
        // Console.WriteLine(localVar);  // Error: localVar not accessible here
    }
}

Value Types vs Reference Types

using System;

class Program
{
    static void Main()
    {
        // Value types (stored on stack)
        int a = 10;
        int b = a;  // Copy of value
        b = 20;
        Console.WriteLine($"a: {a}, b: {b}");  // a=10, b=20
        
        // Reference types (stored on heap)
        int[] array1 = { 1, 2, 3 };
        int[] array2 = array1;  // Copy of reference (both point to same object)
        array2[0] = 100;
        Console.WriteLine($"array1[0]: {array1[0]}");  // 100 (both arrays affected)
        
        // Nullable value types
        int? nullableInt = null;  // Can be null
        nullableInt = 42;
        Console.WriteLine($"Nullable int: {nullableInt}");
        
        // Checking for null
        if (nullableInt.HasValue)
        {
            Console.WriteLine($"Value: {nullableInt.Value}");
        }
        
        // Null-coalescing operator
        int safeValue = nullableInt ?? -1;  // Use -1 if null
        Console.WriteLine($"Safe value: {safeValue}");
    }
}

Type Conversion and Casting

// Implicit conversion (safe, no data loss)
int small = 100;
long large = small;  // Implicit conversion

// Explicit conversion (casting)
double d = 3.14;
int i = (int)d;  // Explicit conversion - truncates to 3

// Convert class
string str = "123";
int parsed = Convert.ToInt32(str);
double converted = Convert.ToDouble("3.14");

// Parse and TryParse
string numberStr = "456";
int result;
bool success = int.TryParse(numberStr, out result);  // Safe parsing

// Boxing and Unboxing
int value = 42;
object boxed = value;  // Boxing - value type to reference type
int unboxed = (int)boxed;  // Unboxing - back to value type

// Type checking
object obj = "Hello";
if (obj is string)
{
    string str = (string)obj;
    Console.WriteLine($"It's a string: {str}");
}

// Pattern matching (C# 7.0+)
if (obj is string s)
{
    Console.WriteLine($"It's a string: {s}");
}

Common Pitfalls

  • Uninitialized local variables cause compile errors
  • Using variables outside their scope
  • Confusing value types with reference types
  • Forgetting that string is a reference type but has value semantics

Conditional Statements: if, else if, else

Conditional statements allow your program to make decisions and execute different code blocks based on conditions.

Basic if Statement

using System;

class Program
{
    static void Main()
    {
        Console.Write("Enter a number: ");
        int number = int.Parse(Console.ReadLine());
        
        // Simple if statement
        if (number > 0)
        {
            Console.WriteLine("The number is positive");
        }
        
        // if-else statement
        if (number % 2 == 0)
        {
            Console.WriteLine("The number is even");
        }
        else
        {
            Console.WriteLine("The number is odd");
        }
        
        // Multiple conditions
        if (number > 0 && number < 100)
        {
            Console.WriteLine("The number is between 1 and 99");
        }
    }
}

if-else if-else Chain

Console.Write("Enter your score (0-100): ");
int score = int.Parse(Console.ReadLine());

if (score >= 90)
{
    Console.WriteLine("Grade: A");
}
else if (score >= 80)
{
    Console.WriteLine("Grade: B");
}
else if (score >= 70)
{
    Console.WriteLine("Grade: C");
}
else if (score >= 60)
{
    Console.WriteLine("Grade: D");
}
else
{
    Console.WriteLine("Grade: F");
}

// Switch expression (C# 8.0+)
string grade = score switch
{
    >= 90 => "A",
    >= 80 => "B",
    >= 70 => "C",
    >= 60 => "D",
    _ => "F"
};
Console.WriteLine($"Grade: {grade}");

Nested if Statements

Console.Write("Enter your age: ");
int age = int.Parse(Console.ReadLine());
Console.Write("Do you have a license? (true/false): ");
bool hasLicense = bool.Parse(Console.ReadLine());

if (age >= 18)
{
    if (hasLicense)
    {
        Console.WriteLine("You can drive legally");
    }
    else
    {
        Console.WriteLine("You need to get a license first");
    }
}
else
{
    Console.WriteLine("You are too young to drive");
}

// Complex conditions
bool isWeekend = DateTime.Now.DayOfWeek == DayOfWeek.Saturday || 
                 DateTime.Now.DayOfWeek == DayOfWeek.Sunday;
int hour = DateTime.Now.Hour;

if (isWeekend && (hour >= 10 && hour <= 22))
{
    Console.WriteLine("Store is open on weekend");
}
else if (!isWeekend && (hour >= 9 && hour <= 21))
{
    Console.WriteLine("Store is open on weekday");
}
else
{
    Console.WriteLine("Store is closed");
}

Conditional Operator (Ternary)

int a = 10, b = 20;
        
// Traditional if-else
int max;
if (a > b)
{
    max = a;
}
else
{
    max = b;
}
        
// Using ternary operator
int max2 = (a > b) ? a : b;
        
Console.WriteLine($"Maximum: {max2}");
        
// Nested ternary (use sparingly!)
int x = 5, y = 10, z = 15;
int largest = (x > y) ? ((x > z) ? x : z) : ((y > z) ? y : z);
Console.WriteLine($"Largest: {largest}");

// Ternary with method calls
string result = (score >= 60) ? "Pass" : "Fail";
Console.WriteLine($"Result: {result}");

// Null-coalescing with ternary
string input = null;
string output = input != null ? input : "Default";
// Equivalent to:
string output2 = input ?? "Default";

Common Pitfalls

  • Using assignment = instead of equality == (compile error in C#)
  • Forgetting braces {} for multi-statement blocks
  • Dangling else problem (else matches with nearest if)
  • Overusing nested ternary operators reduces readability

for Loop in C#

The for loop is used when you know exactly how many times you want to iterate. It has three parts: initialization, condition, and increment.

Basic for Loop

using System;

class Program
{
    static void Main()
    {
        // Count from 1 to 5
        for (int i = 1; i <= 5; i++)
        {
            Console.WriteLine($"Count: {i}");
        }
        
        // Countdown from 10 to 1
        for (int i = 10; i >= 1; i--)
        {
            Console.Write($"{i} ");
        }
        Console.WriteLine();
        
        // Iterate through array
        int[] numbers = { 2, 4, 6, 8, 10 };
        for (int i = 0; i < numbers.Length; i++)
        {
            Console.Write($"{numbers[i]} ");
        }
        Console.WriteLine();
        
        // Sum of numbers 1 to 100
        int sum = 0;
        for (int i = 1; i <= 100; i++)
        {
            sum += i;
        }
        Console.WriteLine($"Sum of 1 to 100: {sum}");
    }
}

Advanced for Loop Variations

// Multiple variables in initialization
for (int i = 0, j = 10; i < j; i++, j--)
{
    Console.WriteLine($"i={i}, j={j}");
}

// Omitting parts (infinite loop with break)
int count = 0;
for (;;)
{
    Console.Write($"{count} ");
    count++;
    if (count >= 5) break;
}
Console.WriteLine();

// Using different increment steps
for (int i = 0; i < 20; i += 2)
{
    Console.Write($"{i} ");  // Even numbers: 0 2 4 6 8 10 12 14 16 18
}
Console.WriteLine();

// Reverse iteration
string[] names = { "Alice", "Bob", "Charlie", "Diana" };
for (int i = names.Length - 1; i >= 0; i--)
{
    Console.WriteLine($"{i}: {names[i]}");
}

// Nested for loops for 2D array
int[,] matrix = new int[3, 3];
for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3; j++)
    {
        matrix[i, j] = i * 3 + j + 1;
        Console.Write($"{matrix[i, j]} ");
    }
    Console.WriteLine();
}

Pattern Printing with Loops

// Right triangle
int rows = 5;
for (int i = 1; i <= rows; i++)
{
    for (int j = 1; j <= i; j++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}

// Inverted triangle
for (int i = rows; i >= 1; i--)
{
    for (int j = 1; j <= i; j++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}

// Pyramid
for (int i = 1; i <= rows; i++)
{
    // Print spaces
    for (int j = 1; j <= rows - i; j++)
    {
        Console.Write(" ");
    }
    // Print stars
    for (int j = 1; j <= 2 * i - 1; j++)
    {
        Console.Write("*");
    }
    Console.WriteLine();
}

// Multiplication table
Console.WriteLine("Multiplication Table:");
for (int i = 1; i <= 10; i++)
{
    for (int j = 1; j <= 10; j++)
    {
        Console.Write($"{i * j,4}");
    }
    Console.WriteLine();
}

Common Pitfalls

  • Off-by-one errors (using <= instead of <)
  • Modifying loop counter inside the loop body
  • Infinite loops when condition never becomes false
  • Variable scope issues with loop counter declaration

while Loop in C#

The while loop repeats a block of code as long as a condition is true. It's useful when you don't know in advance how many iterations are needed.

Basic while Loop

using System;

class Program
{
    static void Main()
    {
        // Count from 1 to 5
        int i = 1;
        while (i <= 5)
        {
            Console.WriteLine($"Count: {i}");
            i++;
        }
        
        // User input validation
        int number;
        Console.Write("Enter a positive number: ");
        while (!int.TryParse(Console.ReadLine(), out number) || number <= 0)
        {
            Console.Write("Invalid input. Enter a positive number: ");
        }
        Console.WriteLine($"Thank you! You entered: {number}");
        
        // Reading until sentinel value
        int sum = 0;
        int value;
        Console.WriteLine("Enter numbers (0 to stop): ");
        while (true)
        {
            if (int.TryParse(Console.ReadLine(), out value) && value != 0)
            {
                sum += value;
            }
            else if (value == 0)
            {
                break;
            }
        }
        Console.WriteLine($"Sum: {sum}");
    }
}

do-while Loop

// do-while executes at least once
char choice;
do
{
    Console.WriteLine("Menu:");
    Console.WriteLine("1. Option One");
    Console.WriteLine("2. Option Two");
    Console.WriteLine("q. Quit");
    Console.Write("Enter your choice: ");
    choice = Console.ReadKey().KeyChar;
    Console.WriteLine();
    
    switch (choice)
    {
        case '1': 
            Console.WriteLine("Option One selected"); 
            break;
        case '2': 
            Console.WriteLine("Option Two selected"); 
            break;
        case 'q': 
            Console.WriteLine("Goodbye!"); 
            break;
        default: 
            Console.WriteLine("Invalid choice"); 
            break;
    }
} while (choice != 'q');

// Password validation
string password;
do
{
    Console.Write("Enter password (min 8 characters): ");
    password = Console.ReadLine();
} while (password == null || password.Length < 8);

Console.WriteLine("Password accepted!");

// Random number guessing game
Random random = new Random();
int secretNumber = random.Next(1, 101);
int guess;
int attempts = 0;

do
{
    Console.Write("Guess the number (1-100): ");
    if (int.TryParse(Console.ReadLine(), out guess))
    {
        attempts++;
        if (guess < secretNumber)
            Console.WriteLine("Too low!");
        else if (guess > secretNumber)
            Console.WriteLine("Too high!");
    }
} while (guess != secretNumber);

Console.WriteLine($"Congratulations! You guessed it in {attempts} attempts.");

Practical while Loop Examples

// File reading simulation
string[] fileLines = { "Line 1", "Line 2", "Line 3", null, "Line 5" };
int lineIndex = 0;

while (lineIndex < fileLines.Length && fileLines[lineIndex] != null)
{
    Console.WriteLine(fileLines[lineIndex]);
    lineIndex++;
}

// Processing collections until empty
List<string> tasks = new List<string> 
    { "Task 1", "Task 2", "Task 3", "Task 4" };
    
while (tasks.Count > 0)
{
    string currentTask = tasks[0];
    Console.WriteLine($"Processing: {currentTask}");
    tasks.RemoveAt(0);
    
    // Simulate work
    System.Threading.Thread.Sleep(500);
}
Console.WriteLine("All tasks completed!");

// Exponential backoff retry pattern
int retryCount = 0;
int maxRetries = 5;
bool success = false;

while (!success && retryCount < maxRetries)
{
    try
    {
        // Simulate some operation that might fail
        Console.WriteLine($"Attempt {retryCount + 1}");
        if (retryCount < 2)  // Fail first two attempts
            throw new Exception("Temporary failure");
            
        success = true;
        Console.WriteLine("Operation succeeded!");
    }
    catch
    {
        retryCount++;
        if (retryCount < maxRetries)
        {
            int delay = (int)Math.Pow(2, retryCount) * 1000;  // Exponential backoff
            Console.WriteLine($"Waiting {delay}ms before retry...");
            System.Threading.Thread.Sleep(delay);
        }
    }
}

if (!success)
{
    Console.WriteLine("Operation failed after all retries");
}

Common Pitfalls

  • Forgetting to update the loop control variable
  • Infinite loops when condition never becomes false
  • Not validating input in input loops
  • Off-by-one errors in loop conditions

Loop Control Statements

C# provides loop control statements: break, continue, and goto (use sparingly). These statements alter the normal flow of loops.

break Statement

using System;

class Program
{
    static void Main()
    {
        // break exits the loop immediately
        for (int i = 1; i <= 10; i++)
        {
            if (i == 5)
            {
                break;  // Exit loop when i reaches 5
            }
            Console.Write($"{i} ");
        }
        Console.WriteLine();  // Output: 1 2 3 4
        
        // Search example
        int[] numbers = { 2, 4, 6, 8, 10, 12, 14 };
        int target = 8;
        
        for (int i = 0; i < numbers.Length; i++)
        {
            if (numbers[i] == target)
            {
                Console.WriteLine($"Found {target} at position {i}");
                break;  // Stop searching once found
            }
        }
        
        // break in nested loops
        for (int i = 1; i <= 3; i++)
        {
            for (int j = 1; j <= 3; j++)
            {
                if (i * j > 4)
                {
                    Console.WriteLine("Breaking inner loop");
                    break;  // Only breaks inner loop
                }
                Console.WriteLine($"i={i}, j={j}");
            }
        }
    }
}

continue Statement

// continue skips the rest of current iteration
for (int i = 1; i <= 10; i++)
{
    if (i % 2 == 0)
    {
        continue;  // Skip even numbers
    }
    Console.Write($"{i} ");  // Only odd numbers printed
}
Console.WriteLine();  // Output: 1 3 5 7 9

// Input validation with continue
int sum = 0;
for (int i = 0; i < 5; i++)
{
    Console.Write($"Enter positive number {i + 1}: ");
    if (!int.TryParse(Console.ReadLine(), out int num) || num <= 0)
    {
        Console.WriteLine("Invalid number, skipping...");
        i--;  // Don't count this iteration
        continue;
    }
    sum += num;
}
Console.WriteLine($"Sum of positive numbers: {sum}");

// Processing collection with continue
string[] items = { "apple", "123", "banana", "456", "cherry", "789" };
foreach (string item in items)
{
    if (int.TryParse(item, out _))
    {
        continue;  // Skip numbers
    }
    Console.WriteLine($"Processing fruit: {item}");
}

// Complex condition with continue
for (int i = 1; i <= 20; i++)
{
    if (i % 3 == 0 && i % 5 == 0)
    {
        Console.WriteLine("FizzBuzz");
        continue;
    }
    if (i % 3 == 0)
    {
        Console.WriteLine("Fizz");
        continue;
    }
    if (i % 5 == 0)
    {
        Console.WriteLine("Buzz");
        continue;
    }
    Console.WriteLine(i);
}

goto Statement (Use With Caution)

// goto can make code hard to read - use sparingly!
int attempts = 0;

retry:
if (attempts >= 3)
{
    Console.WriteLine("Too many attempts!");
    return;
}

Console.Write("Enter a positive number: ");
if (!int.TryParse(Console.ReadLine(), out int number) || number <= 0)
{
    Console.WriteLine("Invalid input. ");
    attempts++;
    goto retry;
}

Console.WriteLine($"Valid number: {number}");

// goto in switch statements (more acceptable)
int value = 2;
switch (value)
{
    case 1:
        Console.WriteLine("Case 1");
        break;
    case 2:
        Console.WriteLine("Case 2");
        goto case 1;  // Jump to case 1
    default:
        Console.WriteLine("Default");
        break;
}

// Alternative to goto - use methods and loops instead
bool validInput = false;
while (!validInput && attempts < 3)
{
    Console.Write("Enter a positive number: ");
    if (int.TryParse(Console.ReadLine(), out number) && number > 0)
    {
        validInput = true;
    }
    else
    {
        Console.WriteLine("Invalid input.");
        attempts++;
    }
}

if (validInput)
{
    Console.WriteLine($"Valid number: {number}");
}
else
{
    Console.WriteLine("Too many attempts!");
}

Loop Control with return

// return exits the entire method
int FindNumber(int[] array, int target)
{
    for (int i = 0; i < array.Length; i++)
    {
        if (array[i] == target)
        {
            return i;  // Exit method and return position
        }
    }
    return -1;  // Not found
}

// Using return in void methods
void ProcessData(string[] data)
{
    if (data == null || data.Length == 0)
    {
        Console.WriteLine("No data to process");
        return;  // Exit method early
    }
    
    foreach (string item in data)
    {
        if (item == "STOP")
        {
            Console.WriteLine("Stop signal received");
            return;  // Exit method
        }
        Console.WriteLine($"Processing: {item}");
    }
    Console.WriteLine("All data processed");
}

// Test the methods
int[] numbers = { 1, 2, 3, 4, 5 };
int position = FindNumber(numbers, 3);
Console.WriteLine($"Found at position: {position}");

string[] testData = { "A", "B", "STOP", "C" };
ProcessData(testData);

Common Pitfalls

  • Using break when continue is more appropriate
  • Overusing goto creates "spaghetti code"
  • break only exits the innermost loop
  • Placing code after continue that will never execute

Nested Loops in C#

Nested loops are loops within loops, where one loop (the inner loop) runs completely for each iteration of another loop (the outer loop). They're commonly used for multi-dimensional data, pattern printing, and combinatorial problems.

Basic Nested Loops

using System;

class Program
{
    static void Main()
    {
        // Simple nested for loops
        Console.WriteLine("Basic nested loops:");
        for (int i = 1; i <= 3; i++)
        {
            for (int j = 1; j <= 3; j++)
            {
                Console.WriteLine($"Outer: {i}, Inner: {j}");
            }
        }
        
        // Multiplication table
        Console.WriteLine("\nMultiplication Table (1-5):");
        for (int i = 1; i <= 5; i++)
        {
            for (int j = 1; j <= 5; j++)
            {
                Console.Write($"{i * j,4}");
            }
            Console.WriteLine();
        }
        
        // Character patterns
        Console.WriteLine("\nCharacter grid:");
        for (char row = 'A'; row <= 'D'; row++)
        {
            for (int col = 1; col <= 4; col++)
            {
                Console.Write($"{row}{col} ");
            }
            Console.WriteLine();
        }
    }
}

Pattern Printing with Nested Loops

// Right triangle
Console.WriteLine("Right triangle:");
int rows = 5;
for (int i = 1; i <= rows; i++)
{
    for (int j = 1; j <= i; j++)
    {
        Console.Write("* ");
    }
    Console.WriteLine();
}

// Inverted triangle
Console.WriteLine("\nInverted triangle:");
for (int i = rows; i >= 1; i--)
{
    for (int j = 1; j <= i; j++)
    {
        Console.Write("* ");
    }
    Console.WriteLine();
}

// Pyramid pattern
Console.WriteLine("\nPyramid:");
for (int i = 1; i <= rows; i++)
{
    // Print leading spaces
    for (int j = 1; j <= rows - i; j++)
    {
        Console.Write("  ");
    }
    // Print stars
    for (int j = 1; j <= 2 * i - 1; j++)
    {
        Console.Write("* ");
    }
    Console.WriteLine();
}

// Hollow square
Console.WriteLine("\nHollow square:");
int size = 6;
for (int i = 1; i <= size; i++)
{
    for (int j = 1; j <= size; j++)
    {
        if (i == 1 || i == size || j == 1 || j == size)
            Console.Write("* ");
        else
            Console.Write("  ");
    }
    Console.WriteLine();
}

// Number pyramid
Console.WriteLine("\nNumber pyramid:");
for (int i = 1; i <= 5; i++)
{
    // Print spaces
    for (int j = 1; j <= 5 - i; j++)
    {
        Console.Write(" ");
    }
    // Print increasing numbers
    for (int j = 1; j <= i; j++)
    {
        Console.Write(j);
    }
    // Print decreasing numbers
    for (int j = i - 1; j >= 1; j--)
    {
        Console.Write(j);
    }
    Console.WriteLine();
}

Working with Multi-dimensional Arrays

// 2D array processing
int[,] matrix = new int[3, 4] 
{ 
    { 1, 2, 3, 4 },
    { 5, 6, 7, 8 },
    { 9, 10, 11, 12 }
};

Console.WriteLine("2D Array (Row-major order):");
for (int row = 0; row < matrix.GetLength(0); row++)
{
    for (int col = 0; col < matrix.GetLength(1); col++)
    {
        Console.Write($"{matrix[row, col],3}");
    }
    Console.WriteLine();
}

// Column-major traversal
Console.WriteLine("\nColumn-major order:");
for (int col = 0; col < matrix.GetLength(1); col++)
{
    for (int row = 0; row < matrix.GetLength(0); row++)
    {
        Console.Write($"{matrix[row, col],3}");
    }
    Console.WriteLine();
}

// Matrix operations - transpose
Console.WriteLine("\nTransposed matrix:");
for (int col = 0; col < matrix.GetLength(1); col++)
{
    for (int row = 0; row < matrix.GetLength(0); row++)
    {
        Console.Write($"{matrix[row, col],3}");
    }
    Console.WriteLine();
}

// Sum of rows and columns
Console.WriteLine("\nRow and column sums:");
int[] rowSums = new int[matrix.GetLength(0)];
int[] colSums = new int[matrix.GetLength(1)];

for (int row = 0; row < matrix.GetLength(0); row++)
{
    for (int col = 0; col < matrix.GetLength(1); col++)
    {
        rowSums[row] += matrix[row, col];
        colSums[col] += matrix[row, col];
    }
}

Console.Write("Row sums: ");
foreach (int sum in rowSums) Console.Write($"{sum} ");

Console.Write("\nColumn sums: ");
foreach (int sum in colSums) Console.Write($"{sum} ");
Console.WriteLine();

Advanced Nested Loop Patterns

// Pascal's triangle
Console.WriteLine("Pascal's Triangle:");
int n = 6;
for (int i = 0; i < n; i++)
{
    // Print spaces for centering
    for (int j = 0; j < n - i - 1; j++)
    {
        Console.Write("  ");
    }
    
    int number = 1;
    for (int j = 0; j <= i; j++)
    {
        Console.Write($"{number,4}");
        number = number * (i - j) / (j + 1);
    }
    Console.WriteLine();
}

// Diamond pattern
Console.WriteLine("\nDiamond pattern:");
int diamondSize = 5;
// Upper half
for (int i = 1; i <= diamondSize; i++)
{
    for (int j = 1; j <= diamondSize - i; j++)
        Console.Write(" ");
    for (int j = 1; j <= 2 * i - 1; j++)
        Console.Write("*");
    Console.WriteLine();
}
// Lower half
for (int i = diamondSize - 1; i >= 1; i--)
{
    for (int j = 1; j <= diamondSize - i; j++)
        Console.Write(" ");
    for (int j = 1; j <= 2 * i - 1; j++)
        Console.Write("*");
    Console.WriteLine();
}

// Chessboard pattern
Console.WriteLine("\nChessboard:");
for (int i = 1; i <= 8; i++)
{
    for (int j = 1; j <= 8; j++)
    {
        if ((i + j) % 2 == 0)
            Console.Write("■ ");
        else
            Console.Write("□ ");
    }
    Console.WriteLine();
}

// Spiral pattern
Console.WriteLine("\nSpiral numbers:");
int spiralSize = 5;
int[,] spiral = new int[spiralSize, spiralSize];
int current = 1;
int top = 0, bottom = spiralSize - 1, left = 0, right = spiralSize - 1;

while (current <= spiralSize * spiralSize)
{
    // Top row
    for (int i = left; i <= right; i++)
        spiral[top, i] = current++;
    top++;
    
    // Right column
    for (int i = top; i <= bottom; i++)
        spiral[i, right] = current++;
    right--;
    
    // Bottom row
    for (int i = right; i >= left; i--)
        spiral[bottom, i] = current++;
    bottom--;
    
    // Left column
    for (int i = bottom; i >= top; i--)
        spiral[i, left] = current++;
    left++;
}

// Print spiral
for (int i = 0; i < spiralSize; i++)
{
    for (int j = 0; j < spiralSize; j++)
    {
        Console.Write($"{spiral[i, j],3}");
    }
    Console.WriteLine();
}

Practical Applications

// Combinations and permutations
string[] colors = { "Red", "Green", "Blue" };
string[] sizes = { "Small", "Medium", "Large" };

Console.WriteLine("All color-size combinations:");
for (int i = 0; i < colors.Length; i++)
{
    for (int j = 0; j < sizes.Length; j++)
    {
        Console.WriteLine($"{colors[i]} {sizes[j]}");
    }
}

// Time table generation
string[] days = { "Mon", "Tue", "Wed", "Thu", "Fri" };
string[] times = { "9:00", "11:00", "14:00", "16:00" };
string[,] schedule = new string[days.Length, times.Length];

// Initialize schedule
for (int day = 0; day < days.Length; day++)
{
    for (int time = 0; time < times.Length; time++)
    {
        schedule[day, time] = "Available";
    }
}

// Print schedule
Console.WriteLine("\nWeekly Schedule:");
Console.Write("Time\\Day".PadRight(10));
foreach (string day in days) Console.Write($"{day,10}");
Console.WriteLine();

for (int time = 0; time < times.Length; time++)
{
    Console.Write($"{times[time],-10}");
    for (int day = 0; day < days.Length; day++)
    {
        Console.Write($"{schedule[day, time],10}");
    }
    Console.WriteLine();
}

// Search in 2D array
int[,] grid = {
    { 1, 4, 7, 11 },
    { 2, 5, 8, 12 },
    { 3, 6, 9, 13 }
};
int target = 8;
bool found = false;

for (int row = 0; row < grid.GetLength(0) && !found; row++)
{
    for (int col = 0; col < grid.GetLength(1) && !found; col++)
    {
        if (grid[row, col] == target)
        {
            Console.WriteLine($"\nFound {target} at position [{row}, {col}]");
            found = true;
        }
    }
}

if (!found)
{
    Console.WriteLine($"\n{target} not found in the grid");
}

Mixed Loop Types

// for loop inside while loop
Console.WriteLine("for inside while:");
int outerCount = 1;
while (outerCount <= 3)
{
    Console.Write($"Outer {outerCount}: ");
    
    for (int inner = 1; inner <= outerCount; inner++)
    {
        Console.Write($"{inner} ");
    }
    
    Console.WriteLine();
    outerCount++;
}

// while loop inside for loop
Console.WriteLine("\nwhile inside for:");
for (int i = 1; i <= 4; i++)
{
    Console.Write($"Row {i}: ");
    int j = 1;
    while (j <= i)
    {
        Console.Write($"{j} ");
        j++;
    }
    Console.WriteLine();
}

// Nested loops with break and continue
Console.WriteLine("\nNested loops with control statements:");
for (int i = 1; i <= 5; i++)
{
    if (i == 3) continue;  // Skip iteration 3
    
    for (int j = 1; j <= 5; j++)
    {
        if (i * j > 10)
        {
            Console.WriteLine("Breaking inner loop");
            break;  // Only breaks inner loop
        }
        Console.Write($"{i * j,3}");
    }
    Console.WriteLine();
}

Common Pitfalls

  • Confusing inner and outer loop variables (using wrong index)
  • Exponential time complexity with deep nesting
  • Forgetting to reset inner loop variables when needed
  • Using break in inner loop when you want to break outer loop
  • Incorrect loop bounds leading to partial data processing
  • Performance issues with large datasets due to O(n²) complexity

Methods in C#

Methods are reusable blocks of code that perform specific tasks. They help in organizing code, reducing duplication, and making programs more modular.

Method Definition and Declaration

using System;

class Calculator
{
    // Method with return value
    public int Add(int a, int b)
    {
        return a + b;
    }
    
    // Method without return value (void)
    public void DisplayResult(int result)
    {
        Console.WriteLine($"The result is: {result}");
    }
    
    // Static method
    public static double Multiply(double x, double y)
    {
        return x * y;
    }
    
    // Method with multiple parameters
    public string CreateMessage(string name, int age, string city)
    {
        return $"Hello {name}, age {age}, from {city}";
    }
}

class Program
{
    static void Main()
    {
        Calculator calc = new Calculator();
        
        // Calling instance methods
        int sum = calc.Add(5, 3);
        calc.DisplayResult(sum);
        
        // Calling static method
        double product = Calculator.Multiply(4.5, 2.0);
        Console.WriteLine($"Product: {product}");
        
        // Using method with multiple parameters
        string message = calc.CreateMessage("Alice", 25, "New York");
        Console.WriteLine(message);
    }
}

Method Parameters

class ParameterDemo
{
    // Pass by value (default for value types)
    public void IncrementByValue(int x)
    {
        x++;  // Only modifies local copy
        Console.WriteLine($"Inside method: {x}");
    }
    
    // Pass by reference (ref keyword)
    public void IncrementByRef(ref int x)
    {
        x++;  // Modifies original variable
        Console.WriteLine($"Inside method: {x}");
    }
    
    // Out parameter (must be assigned in method)
    public bool TryParseNumber(string input, out int result)
    {
        return int.TryParse(input, out result);
    }
    
    // Params parameter (variable number of arguments)
    public int Sum(params int[] numbers)
    {
        int total = 0;
        foreach (int num in numbers)
        {
            total += num;
        }
        return total;
    }
    
    // Optional parameters with default values
    public void Greet(string name, string greeting = "Hello", bool uppercase = false)
    {
        string message = $"{greeting}, {name}";
        if (uppercase)
            message = message.ToUpper();
        Console.WriteLine(message);
    }
    
    // Named arguments
    public void Configure(string server, int port = 8080, bool ssl = false)
    {
        Console.WriteLine($"Server: {server}, Port: {port}, SSL: {ssl}");
    }
}

class Program
{
    static void Main()
    {
        ParameterDemo demo = new ParameterDemo();
        int number = 5;
        
        // Pass by value
        demo.IncrementByValue(number);
        Console.WriteLine($"After pass by value: {number}");  // Still 5
        
        // Pass by reference
        demo.IncrementByRef(ref number);
        Console.WriteLine($"After pass by reference: {number}");  // Now 6
        
        // Out parameter
        if (demo.TryParseNumber("123", out int parsed))
        {
            Console.WriteLine($"Parsed number: {parsed}");
        }
        
        // Params parameter
        int total1 = demo.Sum(1, 2, 3);
        int total2 = demo.Sum(1, 2, 3, 4, 5);
        int total3 = demo.Sum(new int[] { 10, 20, 30 });
        Console.WriteLine($"Sums: {total1}, {total2}, {total3}");
        
        // Optional parameters
        demo.Greet("Alice");                    // Uses default greeting
        demo.Greet("Bob", "Hi");               // Uses provided greeting
        demo.Greet("Charlie", uppercase: true); // Named argument
        
        // Named arguments
        demo.Configure("example.com");
        demo.Configure("secure.com", ssl: true);
        demo.Configure(server: "test.com", port: 9000);
    }
}

Method Overloading

class MathOperations
{
    // Same method name, different parameters
    public int Multiply(int a, int b)
    {
        return a * b;
    }
    
    public double Multiply(double a, double b)
    {
        return a * b;
    }
    
    public int Multiply(int a, int b, int c)
    {
        return a * b * c;
    }
    
    public decimal Multiply(decimal a, decimal b)
    {
        return a * b;
    }
}

class Display
{
    // Overloaded display methods
    public void Show(int value)
    {
        Console.WriteLine($"Integer: {value}");
    }
    
    public void Show(double value)
    {
        Console.WriteLine($"Double: {value:F2}");
    }
    
    public void Show(string value)
    {
        Console.WriteLine($"String: {value}");
    }
    
    public void Show(string value, int times)
    {
        for (int i = 0; i < times; i++)
        {
            Console.WriteLine(value);
        }
    }
}

class Program
{
    static void Main()
    {
        MathOperations math = new MathOperations();
        Display display = new Display();
        
        Console.WriteLine(math.Multiply(3, 4));        // Calls int version
        Console.WriteLine(math.Multiply(2.5, 3.5));    // Calls double version
        Console.WriteLine(math.Multiply(2, 3, 4));     // Calls three-parameter version
        
        display.Show(10);
        display.Show(3.14159);
        display.Show("Hello");
        display.Show("Repeat", 3);
    }
}

Recursive Methods

class Recursion
{
    // Factorial using recursion
    public long Factorial(int n)
    {
        if (n <= 1) return 1;           // Base case
        return n * Factorial(n - 1);    // Recursive case
    }
    
    // Fibonacci sequence
    public int Fibonacci(int n)
    {
        if (n <= 1) return n;           // Base cases
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
    
    // Recursive directory search
    public void ListFiles(string path, int indent = 0)
    {
        try
        {
            string indentString = new string(' ', indent * 2);
            
            // List files in current directory
            foreach (string file in Directory.GetFiles(path))
            {
                Console.WriteLine($"{indentString}📄 {Path.GetFileName(file)}");
            }
            
            // Recursively list subdirectories
            foreach (string dir in Directory.GetDirectories(path))
            {
                Console.WriteLine($"{indentString}📁 {Path.GetFileName(dir)}");
                ListFiles(dir, indent + 1);  // Recursive call
            }
        }
        catch (UnauthorizedAccessException)
        {
            Console.WriteLine($"{new string(' ', indent * 2)}🚫 Access denied");
        }
    }
}

class Program
{
    static void Main()
    {
        Recursion rec = new Recursion();
        
        Console.WriteLine($"Factorial of 5: {rec.Factorial(5)}");
        
        Console.WriteLine("Fibonacci sequence:");
        for (int i = 0; i < 10; i++)
        {
            Console.Write($"{rec.Fibonacci(i)} ");
        }
        Console.WriteLine();
        
        // List files recursively (commented to avoid file system access in example)
        // rec.ListFiles(@"C:\Example");
    }
}

Common Pitfalls

  • Forgetting return statement in non-void methods
  • Method signature mismatch in overloading
  • Infinite recursion without proper base case
  • Optional parameters must be at the end of parameter list

Using Directives in C#

Using directives import namespaces, making types available without fully qualifying them. They help organize code and reduce typing.

Standard Library Using Directives

// Common using directives
using System;                  // Basic types and console
using System.Collections.Generic;  // Generic collections
using System.Linq;             // LINQ operations
using System.Text;             // StringBuilder and encoding
using System.IO;               // File operations
using System.Threading.Tasks;  // Async programming

class Program
{
    static void Main()
    {
        // Examples using different namespaces
        List<string> names = new List<string> { "Alice", "Bob" };
        StringBuilder sb = new StringBuilder();
        string[] files = Directory.GetFiles(".");
        
        Console.WriteLine("Using directives make code cleaner!");
        
        // Without using System.Text;
        // System.Text.StringBuilder sb2 = new System.Text.StringBuilder();
    }
}

Creating and Using Custom Namespaces

// MathUtils.cs (in separate file)
namespace MyCompany.MathUtilities
{
    public class Calculator
    {
        public static double Add(double a, double b) => a + b;
        public static double Multiply(double a, double b) => a * b;
    }
    
    public class Geometry
    {
        public static double CircleArea(double radius) => Math.PI * radius * radius;
        public static double RectangleArea(double width, double height) => width * height;
    }
}

// StringUtils.cs
namespace MyCompany.StringUtilities
{
    public static class StringExtensions
    {
        public static string Reverse(this string str)
        {
            char[] chars = str.ToCharArray();
            Array.Reverse(chars);
            return new string(chars);
        }
        
        public static int WordCount(this string str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, 
                StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }
}

// Program.cs
using System;
using MyCompany.MathUtilities;
using MyCompany.StringUtilities;
using static MyCompany.MathUtilities.Calculator;  // Static using

class Program
{
    static void Main()
    {
        // Using classes from custom namespaces
        double area = Geometry.CircleArea(5.0);
        double sum = Add(10.5, 20.3);  // From static using
        
        string text = "Hello World";
        string reversed = text.Reverse();  // Extension method
        int words = text.WordCount();
        
        Console.WriteLine($"Circle area: {area:F2}");
        Console.WriteLine($"Sum: {sum}");
        Console.WriteLine($"Reversed: {reversed}");
        Console.WriteLine($"Word count: {words}");
    }
}

Using Aliases and Global Using

// Using alias for disambiguation
using System;
using System.IO;
using MyIO = System.IO;  // Alias

// Using alias for generic types
using StringList = System.Collections.Generic.List<string>;

class Program
{
    static void Main()
    {
        // Using the alias
        MyIO.FileInfo file = new MyIO.FileInfo("test.txt");
        StringList names = new StringList { "Alice", "Bob" };
        
        Console.WriteLine($"File: {file.Name}");
        Console.WriteLine($"Names count: {names.Count}");
    }
}

// Global using directives (C# 10+)
// GlobalUsing.cs
global using System;
global using System.Collections.Generic;
global using System.Linq;

// Now these namespaces are available in all files
class AnotherClass
{
    public void Process()
    {
        List<int> numbers = new List<int> { 1, 2, 3 };
        var evens = numbers.Where(n => n % 2 == 0).ToList();
        Console.WriteLine("Global using makes code cleaner!");
    }
}

Using Statement for Resource Management

using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;

class ResourceManagement
{
    // Using statement for automatic disposal
    public void ReadFile(string path)
    {
        using (FileStream fs = new FileStream(path, FileMode.Open))
        using (StreamReader reader = new StreamReader(fs))
        {
            string content = reader.ReadToEnd();
            Console.WriteLine(content);
        }  // Both fs and reader are automatically disposed
    }
    
    // Using declaration (C# 8.0+)
    public void WriteFile(string path, string content)
    {
        using var writer = new StreamWriter(path);
        writer.WriteLine(content);
        // writer is automatically disposed at the end of the method
    }
    
    // Async using
    public async Task<string> DownloadContentAsync(string url)
    {
        using var client = new HttpClient();
        using var response = await client.GetAsync(url);
        return await response.Content.ReadAsStringAsync();
    }
    
    // Custom disposable class
    public class Timer : IDisposable
    {
        private System.Diagnostics.Stopwatch _stopwatch;
        
        public Timer()
        {
            _stopwatch = System.Diagnostics.Stopwatch.StartNew();
            Console.WriteLine("Timer started");
        }
        
        public void Dispose()
        {
            _stopwatch.Stop();
            Console.WriteLine($"Elapsed: {_stopwatch.ElapsedMilliseconds}ms");
        }
    }
}

class Program
{
    static void Main()
    {
        var rm = new ResourceManagement();
        
        // Using custom disposable
        using (var timer = new ResourceManagement.Timer())
        {
            // Simulate work
            System.Threading.Thread.Sleep(1000);
        }  // Timer stopped automatically
        
        // File operations with using
        try
        {
            rm.WriteFile("test.txt", "Hello, World!");
            rm.ReadFile("test.txt");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error: {ex.Message}");
        }
    }
}

Common Pitfalls

  • Name conflicts when multiple namespaces have same type names
  • Forgetting to dispose resources without using statement
  • Overusing global using directives can cause name pollution
  • Circular namespace references

File Input/Output in C#

C# provides comprehensive file I/O capabilities through the System.IO namespace for reading from and writing to files.

Basic File Operations

using System;
using System.IO;

class Program
{
    static void Main()
    {
        string filePath = "example.txt";
        
        // Writing to a file
        try
        {
            using (StreamWriter writer = new StreamWriter(filePath))
            {
                writer.WriteLine("Hello, File!");
                writer.WriteLine("This is line 2");
                writer.WriteLine($"Number: {42}");
            }
            Console.WriteLine("File written successfully");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error writing file: {ex.Message}");
        }
        
        // Reading from a file
        try
        {
            using (StreamReader reader = new StreamReader(filePath))
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    Console.WriteLine(line);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Error reading file: {ex.Message}");
        }
        
        // File class static methods
        if (File.Exists(filePath))
        {
            string content = File.ReadAllText(filePath);
            Console.WriteLine($"File content: {content}");
            
            string[] lines = File.ReadAllLines(filePath);
            Console.WriteLine($"Number of lines: {lines.Length}");
        }
    }
}

Different File Operations

using System;
using System.IO;
using System.Text;

class FileOperations
{
    public void DemonstrateFileMethods()
    {
        string filePath = "data.txt";
        
        // Append to file
        File.AppendAllText(filePath, "\nAppended line");
        
        // Copy file
        File.Copy(filePath, "data_backup.txt", overwrite: true);
        
        // File information
        FileInfo fileInfo = new FileInfo(filePath);
        Console.WriteLine($"File size: {fileInfo.Length} bytes");
        Console.WriteLine($"Created: {fileInfo.CreationTime}");
        Console.WriteLine($"Last modified: {fileInfo.LastWriteTime}");
        
        // File attributes
        fileInfo.Attributes |= FileAttributes.ReadOnly;
        Console.WriteLine($"Is read-only: {fileInfo.IsReadOnly}");
        
        // Move file
        File.Move(filePath, "moved_data.txt", overwrite: true);
        
        // Delete file
        File.Delete("data_backup.txt");
    }
    
    public void WorkWithDirectories()
    {
        string dirPath = "TestDirectory";
        
        // Create directory
        Directory.CreateDirectory(dirPath);
        
        // Create subdirectories
        Directory.CreateDirectory(Path.Combine(dirPath, "Sub1"));
        Directory.CreateDirectory(Path.Combine(dirPath, "Sub2"));
        
        // List files and directories
        string[] files = Directory.GetFiles(".");
        string[] directories = Directory.GetDirectories(".");
        
        Console.WriteLine("Files in current directory:");
        foreach (string file in files)
        {
            Console.WriteLine($"  {file}");
        }
        
        Console.WriteLine("Directories in current directory:");
        foreach (string dir in directories)
        {
            Console.WriteLine($"  {dir}");
        }
        
        // Directory information
        DirectoryInfo dirInfo = new DirectoryInfo(dirPath);
        Console.WriteLine($"Directory exists: {dirInfo.Exists}");
        Console.WriteLine($"Full path: {dirInfo.FullName}");
        
        // Clean up
        Directory.Delete(dirPath, recursive: true);
    }
}

class Program
{
    static void Main()
    {
        FileOperations ops = new FileOperations();
        ops.DemonstrateFileMethods();
        ops.WorkWithDirectories();
    }
}

Reading Different Data Types

using System;
using System.IO;
using System.Globalization;

class DataFileOperations
{
    public void WriteMixedData()
    {
        using (StreamWriter writer = new StreamWriter("people.csv"))
        {
            // Write header
            writer.WriteLine("Name,Age,Salary,JoinDate");
            
            // Write data
            writer.WriteLine("John Doe,25,55000.50,2020-03-15");
            writer.WriteLine("Jane Smith,30,65000.75,2018-07-22");
            writer.WriteLine("Bob Johnson,35,72000.00,2015-11-30");
        }
    }
    
    public void ReadMixedData()
    {
        using (StreamReader reader = new StreamReader("people.csv"))
        {
            // Skip header
            reader.ReadLine();
            
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                string[] parts = line.Split(',');
                
                string name = parts[0];
                int age = int.Parse(parts[1]);
                decimal salary = decimal.Parse(parts[2], CultureInfo.InvariantCulture);
                DateTime joinDate = DateTime.Parse(parts[3]);
                
                Console.WriteLine($"{name}: Age {age}, Salary {salary:C}, Joined {joinDate:yyyy-MM-dd}");
            }
        }
    }
    
    public void BinaryFileOperations()
    {
        // Write binary data
        using (BinaryWriter writer = new BinaryWriter(File.Open("data.bin", FileMode.Create)))
        {
            writer.Write(42);           // int
            writer.Write(3.14159);      // double
            writer.Write(true);         // bool
            writer.Write("Hello");      // string
        }
        
        // Read binary data
        using (BinaryReader reader = new BinaryReader(File.Open("data.bin", FileMode.Open)))
        {
            int number = reader.ReadInt32();
            double pi = reader.ReadDouble();
            bool flag = reader.ReadBoolean();
            string text = reader.ReadString();
            
            Console.WriteLine($"Number: {number}, Pi: {pi}, Flag: {flag}, Text: {text}");
        }
    }
}

class Program
{
    static void Main()
    {
        DataFileOperations dataOps = new DataFileOperations();
        dataOps.WriteMixedData();
        dataOps.ReadMixedData();
        dataOps.BinaryFileOperations();
    }
}

File Streams and Advanced Operations

using System;
using System.IO;
using System.Text;

class AdvancedFileOperations
{
    public void FileStreamOperations()
    {
        // FileStream for low-level file access
        using (FileStream fs = new FileStream("stream.txt", FileMode.Create, FileAccess.Write))
        {
            string content = "Hello FileStream!";
            byte[] bytes = Encoding.UTF8.GetBytes(content);
            fs.Write(bytes, 0, bytes.Length);
        }
        
        // Reading with FileStream
        using (FileStream fs = new FileStream("stream.txt", FileMode.Open, FileAccess.Read))
        {
            byte[] buffer = new byte[fs.Length];
            fs.Read(buffer, 0, buffer.Length);
            string content = Encoding.UTF8.GetString(buffer);
            Console.WriteLine($"FileStream content: {content}");
        }
    }
    
    public void MemoryStreamOperations()
    {
        // Using MemoryStream for in-memory operations
        using (MemoryStream ms = new MemoryStream())
        {
            using (StreamWriter writer = new StreamWriter(ms, Encoding.UTF8, 1024, true))
            {
                writer.WriteLine("Line 1");
                writer.WriteLine("Line 2");
                writer.Flush();
                
                // Reset position to read from beginning
                ms.Position = 0;
                
                using (StreamReader reader = new StreamReader(ms))
                {
                    string content = reader.ReadToEnd();
                    Console.WriteLine("MemoryStream content:");
                    Console.WriteLine(content);
                }
            }
        }
    }
    
    public void FileMonitoring()
    {
        // Create a FileSystemWatcher
        FileSystemWatcher watcher = new FileSystemWatcher(".");
        watcher.Filter = "*.txt";
        watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
        
        watcher.Created += (s, e) => Console.WriteLine($"Created: {e.Name}");
        watcher.Changed += (s, e) => Console.WriteLine($"Changed: {e.Name}");
        watcher.Deleted += (s, e) => Console.WriteLine($"Deleted: {e.Name}");
        watcher.Renamed += (s, e) => Console.WriteLine($"Renamed: {e.OldName} -> {e.Name}");
        
        watcher.EnableRaisingEvents = true;
        
        Console.WriteLine("Watching for file changes... Press any key to stop.");
        Console.ReadKey();
        
        watcher.EnableRaisingEvents = false;
        watcher.Dispose();
    }
    
    public async Task AsyncFileOperations()
    {
        // Async file operations
        string content = "Hello async world!";
        
        // Write async
        using (StreamWriter writer = new StreamWriter("async.txt"))
        {
            await writer.WriteLineAsync(content);
        }
        
        // Read async
        using (StreamReader reader = new StreamReader("async.txt"))
        {
            string result = await reader.ReadToEndAsync();
            Console.WriteLine($"Async read: {result}");
        }
    }
}

class Program
{
    static async Task Main()
    {
        AdvancedFileOperations advancedOps = new AdvancedFileOperations();
        advancedOps.FileStreamOperations();
        advancedOps.MemoryStreamOperations();
        
        // Start file monitoring
        // advancedOps.FileMonitoring();
        
        await advancedOps.AsyncFileOperations();
    }
}

Common Pitfalls

  • Forgetting to check if file exists before operations
  • Not disposing file streams (use using statements)
  • Mixing text and binary modes incorrectly
  • Assuming file operations always succeed - always handle exceptions

Collections in C#

Collections are data structures that store and manage groups of objects. C# provides a rich set of collection types in the System.Collections and System.Collections.Generic namespaces.

Collection Types Overview

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

class Program
{
    static void Main()
    {
        // List<T> - Dynamic array
        List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
        
        // Dictionary<TKey, TValue> - Key-value pairs
        Dictionary<string, int> ages = new Dictionary<string, int>
        {
            ["Alice"] = 25,
            ["Bob"] = 30,
            ["Charlie"] = 35
        };
        
        // HashSet<T> - Unique elements
        HashSet<int> uniqueNumbers = new HashSet<int> { 1, 2, 3, 2, 1 };
        
        // Queue<T> - FIFO collection
        Queue<string> queue = new Queue<string>();
        queue.Enqueue("First");
        queue.Enqueue("Second");
        
        // Stack<T> - LIFO collection
        Stack<string> stack = new Stack<string>();
        stack.Push("First");
        stack.Push("Second");
        
        Console.WriteLine("List count: " + names.Count);
        Console.WriteLine("Dictionary count: " + ages.Count);
        Console.WriteLine("HashSet count: " + uniqueNumbers.Count); // 3 (duplicates removed)
        Console.WriteLine("Queue count: " + queue.Count);
        Console.WriteLine("Stack count: " + stack.Count);
    }
}

Generic vs Non-Generic Collections

class CollectionComparison
{
    public void DemonstrateGenerics()
    {
        // Non-generic collections (older, not type-safe)
        ArrayList nonGenericList = new ArrayList();
        nonGenericList.Add("Hello");
        nonGenericList.Add(123);        // Mixed types allowed
        nonGenericList.Add(DateTime.Now);
        
        // Requires casting and can cause runtime errors
        string firstItem = (string)nonGenericList[0]; // Works
        // string secondItem = (string)nonGenericList[1]; // Runtime error!
        
        // Generic collections (type-safe)
        List<string> genericList = new List<string>();
        genericList.Add("Hello");
        // genericList.Add(123); // Compile-time error!
        genericList.Add("World");
        
        string firstGeneric = genericList[0]; // No casting needed
        
        Console.WriteLine("Non-generic count: " + nonGenericList.Count);
        Console.WriteLine("Generic count: " + genericList.Count);
    }
    
    public void CommonGenericCollections()
    {
        // List<T> - Most commonly used
        List<int> numbers = new List<int> { 1, 2, 3 };
        
        // Dictionary<TKey, TValue> - Fast lookups by key
        Dictionary<string, string> config = new Dictionary<string, string>
        {
            ["Server"] = "localhost",
            ["Port"] = "8080",
            ["Timeout"] = "30"
        };
        
        // HashSet<T> - High-performance set operations
        HashSet<string> usernames = new HashSet<string>();
        usernames.Add("alice");
        usernames.Add("bob");
        usernames.Add("alice"); // Duplicate - ignored
        
        // LinkedList<T> - Efficient insertions/deletions
        LinkedList<int> linkedList = new LinkedList<int>();
        linkedList.AddLast(1);
        linkedList.AddLast(2);
        linkedList.AddFirst(0);
        
        // Sorted collections
        SortedList<string, int> sortedAges = new SortedList<string, int>
        {
            ["Charlie"] = 35,
            ["Alice"] = 25,
            ["Bob"] = 30
        }; // Automatically sorted by key
        
        SortedSet<int> sortedNumbers = new SortedSet<int> { 3, 1, 4, 1, 5 };
        
        Console.WriteLine("HashSet count: " + usernames.Count); // 2
        Console.WriteLine("SortedList first: " + sortedAges.Keys[0]); // Alice
        Console.WriteLine("SortedSet: " + string.Join(", ", sortedNumbers)); // 1, 3, 4, 5
    }
}

class Program
{
    static void Main()
    {
        CollectionComparison comp = new CollectionComparison();
        comp.DemonstrateGenerics();
        comp.CommonGenericCollections();
    }
}

Collection Interfaces

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

class InterfaceDemo
{
    public void DemonstrateInterfaces()
    {
        // Using interfaces for flexibility
        IList<string> list = new List<string> { "A", "B", "C" };
        IDictionary<string, int> dict = new Dictionary<string, int>();
        ICollection<int> collection = new HashSet<int> { 1, 2, 3 };
        
        // Common interface methods
        list.Add("D");
        dict.Add("Key", 42);
        collection.Remove(2);
        
        // IEnumerable - enables foreach
        IEnumerable<string> enumerable = list;
        foreach (string item in enumerable)
        {
            Console.WriteLine(item);
        }
        
        // ICollection - basic collection operations
        Console.WriteLine($"Count: {collection.Count}");
        Console.WriteLine($"Contains 1: {collection.Contains(1)}");
        
        // IList - index-based access
        Console.WriteLine($"Index 1: {list[1]}");
        list.Insert(1, "Inserted");
        
        // IDictionary - key-based access
        dict["Another"] = 100;
        Console.WriteLine($"Key value: {dict["Key"]}");
    }
    
    public void CustomCollection<T> : ICollection<T>
    {
        private List<T> _items = new List<T>();
        
        public int Count => _items.Count;
        public bool IsReadOnly => false;
        
        public void Add(T item) => _items.Add(item);
        public void Clear() => _items.Clear();
        public bool Contains(T item) => _items.Contains(item);
        public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);
        public bool Remove(T item) => _items.Remove(item);
        
        public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();
        IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
    }
}

class Program
{
    static void Main()
    {
        InterfaceDemo demo = new InterfaceDemo();
        demo.DemonstrateInterfaces();
        
        var custom = new InterfaceDemo.CustomCollection<int>();
        custom.Add(1);
        custom.Add(2);
        custom.Add(3);
        
        foreach (int item in custom)
        {
            Console.WriteLine($"Custom collection: {item}");
        }
    }
}

Common Pitfalls

  • Using non-generic collections instead of generic ones
  • Not checking for key existence before accessing dictionaries
  • Modifying collections during enumeration
  • Using wrong collection type for the use case

Collection Types in C#

C# provides specialized collection types optimized for different scenarios including lists, dictionaries, sets, queues, and stacks.

List and Dictionary Operations

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

class CollectionOperations
{
    public void ListOperations()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 2, 3 };
        
        // Adding elements
        numbers.Add(6);
        numbers.AddRange(new[] { 7, 8, 9 });
        numbers.Insert(0, 0); // Insert at beginning
        
        // Removing elements
        numbers.Remove(2); // Remove first occurrence of 2
        numbers.RemoveAt(0); // Remove element at index 0
        numbers.RemoveAll(n => n % 2 == 0); // Remove all even numbers
        numbers.RemoveRange(1, 2); // Remove 2 elements starting from index 1
        
        // Finding elements
        int firstEven = numbers.Find(n => n % 2 == 0);
        List<int> allEven = numbers.FindAll(n => n % 2 == 0);
        int index = numbers.IndexOf(3);
        int lastIndex = numbers.LastIndexOf(3);
        
        // Sorting and searching
        numbers.Sort();
        numbers.Reverse();
        bool exists = numbers.Exists(n => n > 10);
        bool allPositive = numbers.TrueForAll(n => n > 0);
        
        // Conversion
        int[] array = numbers.ToArray();
        List<string> strings = numbers.ConvertAll(n => n.ToString());
        
        Console.WriteLine("List operations completed");
        Console.WriteLine($"Count: {numbers.Count}, Capacity: {numbers.Capacity}");
    }
    
    public void DictionaryOperations()
    {
        Dictionary<string, int> studentGrades = new Dictionary<string, int>
        {
            ["Alice"] = 85,
            ["Bob"] = 92,
            ["Charlie"] = 78
        };
        
        // Adding and updating
        studentGrades["Diana"] = 88; // Add new
        studentGrades["Alice"] = 90; // Update existing
        
        // Safe access
        if (studentGrades.TryGetValue("Bob", out int bobsGrade))
        {
            Console.WriteLine($"Bob's grade: {bobsGrade}");
        }
        
        // Checking existence
        if (!studentGrades.ContainsKey("Eve"))
        {
            studentGrades["Eve"] = 95;
        }
        
        // Removing
        studentGrades.Remove("Charlie");
        
        // Iterating
        foreach (var pair in studentGrades)
        {
            Console.WriteLine($"{pair.Key}: {pair.Value}");
        }
        
        foreach (string name in studentGrades.Keys)
        {
            Console.WriteLine($"Student: {name}");
        }
        
        foreach (int grade in studentGrades.Values)
        {
            Console.WriteLine($"Grade: {grade}");
        }
        
        // Dictionary with custom comparer
        Dictionary<string, int> caseInsensitive = new Dictionary<string, int>(
            StringComparer.OrdinalIgnoreCase);
        
        caseInsensitive["Alice"] = 85;
        Console.WriteLine(caseInsensitive["ALICE"]); // 85 (case insensitive)
    }
}

class Program
{
    static void Main()
    {
        CollectionOperations ops = new CollectionOperations();
        ops.ListOperations();
        ops.DictionaryOperations();
    }
}

Set, Queue, and Stack Operations

using System;
using System.Collections.Generic;

class SpecializedCollections
{
    public void SetOperations()
    {
        HashSet<int> set1 = new HashSet<int> { 1, 2, 3, 4, 5 };
        HashSet<int> set2 = new HashSet<int> { 4, 5, 6, 7, 8 };
        
        // Set operations
        set1.UnionWith(set2);          // set1 becomes {1,2,3,4,5,6,7,8}
        set1.IntersectWith(set2);      // set1 becomes {4,5,6,7,8}
        set1.ExceptWith(new[] { 6, 7 }); // set1 becomes {4,5,8}
        
        // Set comparisons
        bool isSubset = set1.IsSubsetOf(set2);        // True
        bool isSuperset = set1.IsSupersetOf(set2);    // False
        bool overlaps = set1.Overlaps(new[] { 5, 9 }); // True
        bool equals = set1.SetEquals(new[] { 4, 5, 8 }); // True
        
        // SortedSet - automatically sorted
        SortedSet<int> sorted = new SortedSet<int> { 5, 2, 8, 1, 9 };
        Console.WriteLine($"Sorted: {string.Join(", ", sorted)}"); // 1, 2, 5, 8, 9
        Console.WriteLine($"Min: {sorted.Min}, Max: {sorted.Max}");
    }
    
    public void QueueOperations()
    {
        Queue<string> queue = new Queue<string>();
        
        // Enqueue (add to end)
        queue.Enqueue("First");
        queue.Enqueue("Second");
        queue.Enqueue("Third");
        
        // Peek (look at first without removing)
        string first = queue.Peek();
        Console.WriteLine($"First in queue: {first}");
        
        // Dequeue (remove from front)
        while (queue.Count > 0)
        {
            string item = queue.Dequeue();
            Console.WriteLine($"Processing: {item}");
        }
        
        // ConcurrentQueue for thread-safe operations
        System.Collections.Concurrent.ConcurrentQueue<int> concurrentQueue = 
            new System.Collections.Concurrent.ConcurrentQueue<int>();
        
        concurrentQueue.Enqueue(1);
        if (concurrentQueue.TryDequeue(out int result))
        {
            Console.WriteLine($"Dequeued: {result}");
        }
    }
    
    public void StackOperations()
    {
        Stack<string> stack = new Stack<string>();
        
        // Push (add to top)
        stack.Push("First");
        stack.Push("Second");
        stack.Push("Third");
        
        // Peek (look at top without removing)
        string top = stack.Peek();
        Console.WriteLine($"Top of stack: {top}");
        
        // Pop (remove from top)
        while (stack.Count > 0)
        {
            string item = stack.Pop();
            Console.WriteLine($"Popped: {item}");
        }
        
        // Real-world example: Undo/Redo functionality
        Stack<string> undoStack = new Stack<string>();
        Stack<string> redoStack = new Stack<string>();
        
        void DoAction(string action)
        {
            undoStack.Push(action);
            redoStack.Clear(); // Clear redo stack when new action is performed
            Console.WriteLine($"Action: {action}");
        }
        
        void Undo()
        {
            if (undoStack.Count > 0)
            {
                string action = undoStack.Pop();
                redoStack.Push(action);
                Console.WriteLine($"Undone: {action}");
            }
        }
        
        void Redo()
        {
            if (redoStack.Count > 0)
            {
                string action = redoStack.Pop();
                undoStack.Push(action);
                Console.WriteLine($"Redone: {action}");
            }
        }
        
        DoAction("Type Hello");
        DoAction("Bold text");
        DoAction("Insert image");
        
        Undo(); // Undo "Insert image"
        Undo(); // Undo "Bold text"
        Redo(); // Redo "Bold text"
    }
}

class Program
{
    static void Main()
    {
        SpecializedCollections collections = new SpecializedCollections();
        collections.SetOperations();
        collections.QueueOperations();
        collections.StackOperations();
    }
}

Thread-Safe Collections

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

class ThreadSafeCollections
{
    public void ConcurrentCollections()
    {
        // ConcurrentDictionary - thread-safe dictionary
        ConcurrentDictionary<string, int> concurrentDict = new ConcurrentDictionary<string, int>();
        
        Parallel.For(0, 1000, i =>
        {
            string key = $"key_{i % 10}";
            concurrentDict.AddOrUpdate(key, 1, (k, v) => v + 1);
        });
        
        Console.WriteLine("ConcurrentDictionary results:");
        foreach (var pair in concurrentDict)
        {
            Console.WriteLine($"{pair.Key}: {pair.Value}");
        }
        
        // ConcurrentQueue - thread-safe queue
        ConcurrentQueue<int> concurrentQueue = new ConcurrentQueue<int>();
        ConcurrentStack<int> concurrentStack = new ConcurrentStack<int>();
        ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
        
        // Producer-Consumer pattern with BlockingCollection
        BlockingCollection<int> blockingCollection = new BlockingCollection<int>(boundedCapacity: 10);
        
        // Producer task
        Task producer = Task.Run(() =>
        {
            for (int i = 0; i < 20; i++)
            {
                blockingCollection.Add(i);
                Console.WriteLine($"Produced: {i}");
                Thread.Sleep(100);
            }
            blockingCollection.CompleteAdding();
        });
        
        // Consumer task
        Task consumer = Task.Run(() =>
        {
            foreach (int item in blockingCollection.GetConsumingEnumerable())
            {
                Console.WriteLine($"Consumed: {item}");
                Thread.Sleep(150);
            }
        });
        
        Task.WaitAll(producer, consumer);
        Console.WriteLine("Producer-Consumer completed");
    }
    
    public void PerformanceComparison()
    {
        const int iterations = 1000000;
        
        // Regular dictionary (not thread-safe)
        Dictionary<int, int> regularDict = new Dictionary<int, int>();
        var regularLock = new object();
        
        // Concurrent dictionary (thread-safe)
        ConcurrentDictionary<int, int> concurrentDict = new ConcurrentDictionary<int, int>();
        
        // Test with lock
        var watch = System.Diagnostics.Stopwatch.StartNew();
        Parallel.For(0, iterations, i =>
        {
            lock (regularLock)
            {
                regularDict[i] = i * 2;
            }
        });
        watch.Stop();
        Console.WriteLine($"Dictionary with lock: {watch.ElapsedMilliseconds}ms");
        
        // Test concurrent dictionary
        watch.Restart();
        Parallel.For(0, iterations, i =>
        {
            concurrentDict[i] = i * 2;
        });
        watch.Stop();
        Console.WriteLine($"ConcurrentDictionary: {watch.ElapsedMilliseconds}ms");
    }
}

class Program
{
    static void Main()
    {
        ThreadSafeCollections threadSafe = new ThreadSafeCollections();
        threadSafe.ConcurrentCollections();
        threadSafe.PerformanceComparison();
    }
}

Common Pitfalls

  • Using non-thread-safe collections in multi-threaded scenarios
  • Not disposing of BlockingCollection properly
  • Using ConcurrentBag when order matters
  • Forgetting that set operations modify the original collection

LINQ (Language Integrated Query)

LINQ provides a consistent way to query various data sources using SQL-like syntax or method syntax. It works with collections, databases, XML, and more.

LINQ Query Syntax

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

class LinqQuerySyntax
{
    public void BasicQueries()
    {
        List<Person> people = new List<Person>
        {
            new Person("Alice", 25, "New York"),
            new Person("Bob", 30, "London"),
            new Person("Charlie", 35, "New York"),
            new Person("Diana", 28, "Paris"),
            new Person("Eve", 32, "London")
        };
        
        // Query syntax - similar to SQL
        var newYorkers = from person in people
                        where person.City == "New York"
                        select person.Name;
        
        Console.WriteLine("People in New York (query syntax):");
        foreach (string name in newYorkers)
        {
            Console.WriteLine($"  - {name}");
        }
        
        // Complex query with ordering
        var londonSeniors = from person in people
                           where person.City == "London" && person.Age > 30
                           orderby person.Age descending
                           select new { person.Name, person.Age };
        
        Console.WriteLine("Seniors in London:");
        foreach (var person in londonSeniors)
        {
            Console.WriteLine($"  - {person.Name} ({person.Age})");
        }
        
        // Grouping
        var peopleByCity = from person in people
                          group person by person.City into cityGroup
                          select new
                          {
                              City = cityGroup.Key,
                              Count = cityGroup.Count(),
                              AverageAge = cityGroup.Average(p => p.Age)
                          };
        
        Console.WriteLine("People by city:");
        foreach (var group in peopleByCity)
        {
            Console.WriteLine($"  {group.City}: {group.Count} people, avg age {group.AverageAge:F1}");
        }
        
        // Joining (if we had another collection)
        List<Department> departments = new List<Department>
        {
            new Department("Alice", "Engineering"),
            new Department("Bob", "Marketing"),
            new Department("Charlie", "Engineering")
        };
        
        var peopleWithDept = from person in people
                            join dept in departments on person.Name equals dept.EmployeeName
                            select new { person.Name, person.Age, dept.DepartmentName };
        
        Console.WriteLine("People with departments:");
        foreach (var item in peopleWithDept)
        {
            Console.WriteLine($"  - {item.Name} ({item.Age}): {item.DepartmentName}");
        }
    }
}

class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string City { get; set; }
    
    public Person(string name, int age, string city)
    {
        Name = name;
        Age = age;
        City = city;
    }
}

class Department
{
    public string EmployeeName { get; set; }
    public string DepartmentName { get; set; }
    
    public Department(string employee, string department)
    {
        EmployeeName = employee;
        DepartmentName = department;
    }
}

class Program
{
    static void Main()
    {
        LinqQuerySyntax queries = new LinqQuerySyntax();
        queries.BasicQueries();
    }
}

LINQ Method Syntax

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

class LinqMethodSyntax
{
    public void MethodQueries()
    {
        List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        
        // Basic filtering and projection
        var evenSquares = numbers
            .Where(n => n % 2 == 0)
            .Select(n => n * n);
        
        Console.WriteLine("Even squares: " + string.Join(", ", evenSquares));
        
        // Aggregation
        int sum = numbers.Sum();
        double average = numbers.Average();
        int max = numbers.Max();
        int min = numbers.Min();
        
        Console.WriteLine($"Sum: {sum}, Average: {average}, Max: {max}, Min: {min}");
        
        // Element operations
        int firstEven = numbers.First(n => n % 2 == 0);
        int lastEven = numbers.Last(n => n % 2 == 0);
        int singleThree = numbers.Single(n => n == 3); // Throws if multiple or none
        
        // Safe element operations
        int firstOrDefault = numbers.FirstOrDefault(n => n > 100, -1); // Returns -1 if not found
        int singleOrDefault = numbers.SingleOrDefault(n => n == 3); // Returns 0 if not found
        
        // Quantifiers
        bool anyEven = numbers.Any(n => n % 2 == 0);
        bool allPositive = numbers.All(n => n > 0);
        bool containsFive = numbers.Contains(5);
        
        Console.WriteLine($"Any even: {anyEven}, All positive: {allPositive}, Contains 5: {containsFive}");
    }
    
    public void ComplexMethodQueries()
    {
        List<Product> products = new List<Product>
        {
            new Product("Laptop", 999.99m, "Electronics"),
            new Product("Book", 19.99m, "Education"),
            new Product("Phone", 699.99m, "Electronics"),
            new Product("Pen", 2.99m, "Office"),
            new Product("Notebook", 5.99m, "Office")
        };
        
        // Grouping with method syntax
        var productsByCategory = products
            .GroupBy(p => p.Category)
            .Select(g => new
            {
                Category = g.Key,
                Count = g.Count(),
                TotalValue = g.Sum(p => p.Price),
                AveragePrice = g.Average(p => p.Price)
            });
        
        Console.WriteLine("Products by category:");
        foreach (var group in productsByCategory)
        {
            Console.WriteLine($"  {group.Category}: {group.Count} items, " +
                            $"total: {group.TotalValue:C}, avg: {group.AveragePrice:C}");
        }
        
        // Ordering and paging
        var expensiveProducts = products
            .Where(p => p.Price > 100)
            .OrderByDescending(p => p.Price)
            .ThenBy(p => p.Name)
            .Select(p => new { p.Name, Price = p.Price.ToString("C") });
        
        Console.WriteLine("Expensive products:");
        foreach (var product in expensiveProducts)
        {
            Console.WriteLine($"  - {product.Name}: {product.Price}");
        }
        
        // Skip and Take for paging
        int pageSize = 2;
        int pageNumber = 1;
        
        var page = products
            .OrderBy(p => p.Name)
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize);
        
        Console.WriteLine($"Page {pageNumber}:");
        foreach (var product in page)
        {
            Console.WriteLine($"  - {product.Name}");
        }
    }
}

class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
    
    public Product(string name, decimal price, string category)
    {
        Name = name;
        Price = price;
        Category = category;
    }
}

class Program
{
    static void Main()
    {
        LinqMethodSyntax methods = new LinqMethodSyntax();
        methods.MethodQueries();
        methods.ComplexMethodQueries();
    }
}

Advanced LINQ Operations

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

class AdvancedLinq
{
    public void SetOperations()
    {
        int[] set1 = { 1, 2, 3, 4, 5 };
        int[] set2 = { 4, 5, 6, 7, 8 };
        
        // Set operations
        var union = set1.Union(set2);           // {1,2,3,4,5,6,7,8}
        var intersect = set1.Intersect(set2);   // {4,5}
        var except = set1.Except(set2);         // {1,2,3}
        var distinct = set1.Concat(set2).Distinct(); // {1,2,3,4,5,6,7,8}
        
        Console.WriteLine("Union: " + string.Join(", ", union));
        Console.WriteLine("Intersect: " + string.Join(", ", intersect));
        Console.WriteLine("Except: " + string.Join(", ", except));
        Console.WriteLine("Distinct: " + string.Join(", ", distinct));
    }
    
    public void ConversionOperations()
    {
        List<string> names = new List<string> { "Alice", "Bob", "Charlie" };
        
        // Conversion methods
        string[] array = names.ToArray();
        List<string> list = names.ToList();
        Dictionary<string, int> dict = names.ToDictionary(name => name, name => name.Length);
        Lookup<char, string> lookup = names.ToLookup(name => name[0]); // Group by first letter
        
        // Casting and type operations
        List<object> mixed = new List<object> { 1, "hello", 2, "world" };
        var strings = mixed.OfType<string>(); // {"hello", "world"}
        var numbers = mixed.OfType<int>();    // {1, 2}
        
        Console.WriteLine("Strings: " + string.Join(", ", strings));
        Console.WriteLine("Numbers: " + string.Join(", ", numbers));
    }
    
    public void AggregateOperations()
    {
        int[] numbers = { 1, 2, 3, 4, 5 };
        
        // Basic aggregation
        int sum = numbers.Aggregate((acc, n) => acc + n); // 15
        int product = numbers.Aggregate(1, (acc, n) => acc * n); // 120
        
        // Complex aggregation
        string concatenated = numbers.Aggregate("Numbers: ", (acc, n) => acc + n + " ");
        
        // Seed aggregation
        var stats = numbers.Aggregate(
            new { Count = 0, Sum = 0, Min = int.MaxValue, Max = int.MinValue },
            (acc, n) => new
            {
                Count = acc.Count + 1,
                Sum = acc.Sum + n,
                Min = Math.Min(acc.Min, n),
                Max = Math.Max(acc.Max, n)
            });
        
        Console.WriteLine($"Sum: {sum}, Product: {product}");
        Console.WriteLine($"Concatenated: {concatenated}");
        Console.WriteLine($"Stats - Count: {stats.Count}, Sum: {stats.Sum}, Min: {stats.Min}, Max: {stats.Max}");
    }
    
    public void PerformanceConsiderations()
    {
        // Deferred execution
        var numbers = new List<int> { 1, 2, 3, 4, 5 };
        var query = numbers.Where(n => n % 2 == 0).Select(n => n * 2);
        
        Console.WriteLine("Query created but not executed yet");
        numbers.Add(6); // This affects the query result!
        
        Console.WriteLine("Query results: " + string.Join(", ", query)); // 4, 8, 12
        
        // Immediate execution
        var immediate = numbers.Where(n => n % 2 == 0).Select(n => n * 2).ToList();
        numbers.Add(8); // This doesn't affect 'immediate' because it's already executed
        
        Console.WriteLine("Immediate results: " + string.Join(", ", immediate)); // 4, 8, 12
        
        // Performance: Where before Select
        var efficient = numbers
            .Where(n => n % 2 == 0)  // Filter first
            .Select(n => n * 2)      // Then transform
            .ToList();
        
        var inefficient = numbers
            .Select(n => n * 2)      // Transform all
            .Where(n => n % 2 == 0)  // Then filter
            .ToList();
    }
}

class Program
{
    static void Main()
    {
        AdvancedLinq advanced = new AdvancedLinq();
        advanced.SetOperations();
        advanced.ConversionOperations();
        advanced.AggregateOperations();
        advanced.PerformanceConsiderations();
    }
}

Common Pitfalls

  • Forgetting that LINQ uses deferred execution
  • Multiple enumeration of the same query
  • Using First() instead of FirstOrDefault()
  • Not understanding the performance implications of query order

Classes and Objects in C#

Classes are blueprints for creating objects that encapsulate data and behavior. C# supports object-oriented programming with features like encapsulation, inheritance, and polymorphism.

Basic Class Definition

using System;

namespace OOPIntroduction
{
    // Basic class definition
    public class Person
    {
        // Fields (private by convention)
        private string _name;
        private int _age;
        
        // Properties (public interface)
        public string Name
        {
            get => _name;
            set
            {
                if (!string.IsNullOrWhiteSpace(value))
                    _name = value;
            }
        }
        
        public int Age
        {
            get => _age;
            set
            {
                if (value >= 0 && value <= 150)
                    _age = value;
            }
        }
        
        // Auto-implemented property
        public string Email { get; set; }
        
        // Constructors
        public Person(string name, int age)
        {
            Name = name;
            Age = age;
        }
        
        // Default constructor
        public Person() : this("Unknown", 0) { }
        
        // Copy constructor
        public Person(Person other) : this(other.Name, other.Age) { }
        
        // Methods
        public virtual void Introduce()
        {
            Console.WriteLine($"Hello, I'm {Name} and I'm {Age} years old.");
        }
        
        public void CelebrateBirthday()
        {
            Age++;
            Console.WriteLine($"Happy birthday {Name}! You are now {Age}.");
        }
        
        // Static method
        public static void DisplaySpecies()
        {
            Console.WriteLine("Species: Homo sapiens");
        }
    }
    
    class Program
    {
        static void Main()
        {
            // Creating objects
            Person person1 = new Person("Alice", 25);
            Person person2 = new Person("Bob", 30);
            Person person3 = new Person(); // Uses default constructor
            
            // Using properties and methods
            person1.Introduce();
            person1.CelebrateBirthday();
            
            person2.Email = "bob@example.com";
            Console.WriteLine($"{person2.Name}'s email: {person2.Email}");
            
            // Using static method
            Person.DisplaySpecies();
            
            // Object initializer syntax
            Person person4 = new Person
            {
                Name = "Charlie",
                Age = 35,
                Email = "charlie@example.com"
            };
            person4.Introduce();
        }
    }
}

Inheritance in C#

Inheritance allows a class to inherit properties and behaviors from another class. This promotes code reuse and establishes hierarchical relationships.

Basic Inheritance

using System;

namespace InheritanceDemo
{
    // Base class
    public class Animal
    {
        // Protected fields - accessible by derived classes
        protected string _name;
        protected int _age;
        
        // Properties
        public string Name 
        { 
            get => _name; 
            set => _name = value ?? "Unknown";
        }
        
        public int Age 
        { 
            get => _age; 
            set => _age = value >= 0 ? value : 0;
        }
        
        // Constructor
        public Animal(string name, int age)
        {
            Name = name;
            Age = age;
        }
        
        // Virtual method - can be overridden
        public virtual void Speak()
        {
            Console.WriteLine($"{Name} makes a generic animal sound.");
        }
        
        // Regular method
        public void Eat()
        {
            Console.WriteLine($"{Name} is eating.");
        }
        
        // Virtual property
        public virtual string Habitat => "Various habitats";
        
        // Virtual destructor pattern
        public virtual void Dispose()
        {
            Console.WriteLine($"{Name} the animal is being disposed.");
        }
    }
    
    // Derived class - inherits from Animal
    public class Dog : Animal
    {
        // Additional field
        private string _breed;
        
        // Additional property
        public string Breed
        {
            get => _breed;
            set => _breed = value ?? "Mixed";
        }
        
        // Constructor calling base constructor
        public Dog(string name, int age, string breed) : base(name, age)
        {
            Breed = breed;
        }
        
        // Override virtual method
        public override void Speak()
        {
            Console.WriteLine($"{Name} barks: Woof! Woof!");
        }
        
        // Override virtual property
        public override string Habitat => "Domestic homes";
        
        // New method specific to Dog
        public void Fetch()
        {
            Console.WriteLine($"{Name} is fetching the ball.");
        }
        
        // Override ToString for better representation
        public override string ToString()
        {
            return $"{Name} is a {Age}-year-old {Breed} dog";
        }
    }
    
    // Another derived class
    public class Cat : Animal
    {
        public bool IsIndoor { get; set; }
        
        public Cat(string name, int age, bool isIndoor = true) : base(name, age)
        {
            IsIndoor = isIndoor;
        }
        
        public override void Speak()
        {
            Console.WriteLine($"{Name} meows: Meow! Meow!");
        }
        
        public override string Habitat => IsIndoor ? "Indoor homes" : "Outdoor/Indoor";
        
        public void Climb()
        {
            Console.WriteLine($"{Name} is climbing a tree.");
        }
        
        public override string ToString()
        {
            return $"{Name} is a {Age}-year-old {(IsIndoor ? "indoor" : "outdoor")} cat";
        }
    }
    
    class Program
    {
        static void Main()
        {
            // Create instances
            Dog myDog = new Dog("Buddy", 3, "Golden Retriever");
            Cat myCat = new Cat("Whiskers", 2, true);
            
            // Use inherited methods
            myDog.Speak();
            myDog.Eat();
            myDog.Fetch();
            Console.WriteLine(myDog.ToString());
            Console.WriteLine($"Habitat: {myDog.Habitat}");
            
            Console.WriteLine();
            
            myCat.Speak();
            myCat.Eat();
            myCat.Climb();
            Console.WriteLine(myCat.ToString());
            Console.WriteLine($"Habitat: {myCat.Habitat}");
            
            // Polymorphism through base class reference
            Console.WriteLine("\n--- Polymorphism Example ---");
            Animal[] animals = { myDog, myCat, new Animal("Generic", 1) };
            
            foreach (Animal animal in animals)
            {
                animal.Speak(); // Calls appropriate overridden method
                Console.WriteLine($"This animal lives in: {animal.Habitat}");
                Console.WriteLine();
            }
        }
    }
}

Abstract Classes and Interfaces

using System;
using System.Collections.Generic;

namespace AbstractAndInterfaces
{
    // Abstract class - cannot be instantiated
    public abstract class Shape
    {
        // Abstract method - must be implemented by derived classes
        public abstract double CalculateArea();
        
        // Abstract property
        public abstract string Name { get; }
        
        // Virtual method with implementation
        public virtual void Display()
        {
            Console.WriteLine($"Shape: {Name}, Area: {CalculateArea():F2}");
        }
        
        // Regular method with implementation
        public void Describe()
        {
            Console.WriteLine($"I am a {Name} with area {CalculateArea():F2}");
        }
    }
    
    // Interface - defines a contract
    public interface IDrawable
    {
        void Draw();
        string Color { get; set; }
    }
    
    public interface IResizable
    {
        void Resize(double factor);
    }
    
    // Concrete class implementing abstract class and interfaces
    public class Circle : Shape, IDrawable, IResizable
    {
        public double Radius { get; private set; }
        public override string Name => "Circle";
        public string Color { get; set; }
        
        public Circle(double radius)
        {
            Radius = radius;
            Color = "Black";
        }
        
        public override double CalculateArea()
        {
            return Math.PI * Radius * Radius;
        }
        
        public void Draw()
        {
            Console.WriteLine($"Drawing a {Color} circle with radius {Radius}");
        }
        
        public void Resize(double factor)
        {
            if (factor > 0)
            {
                Radius *= factor;
                Console.WriteLine($"Circle resized to radius {Radius}");
            }
        }
    }
    
    public class Rectangle : Shape, IDrawable
    {
        public double Width { get; set; }
        public double Height { get; set; }
        public override string Name => "Rectangle";
        public string Color { get; set; }
        
        public Rectangle(double width, double height)
        {
            Width = width;
            Height = height;
            Color = "Blue";
        }
        
        public override double CalculateArea()
        {
            return Width * Height;
        }
        
        public override void Display()
        {
            Console.WriteLine($"Rectangle: {Width}x{Height}, Area: {CalculateArea():F2}");
        }
        
        public void Draw()
        {
            Console.WriteLine($"Drawing a {Color} rectangle {Width}x{Height}");
        }
    }
    
    // Multiple interface implementation
    public class AdvancedCircle : Circle, IResizable, ICloneable
    {
        public AdvancedCircle(double radius) : base(radius) { }
        
        public object Clone()
        {
            return new AdvancedCircle(Radius) { Color = this.Color };
        }
    }
    
    class Program
    {
        static void Main()
        {
            // Cannot create instance of abstract class
            // Shape shape = new Shape(); // Compile error!
            
            // Using concrete classes
            Circle circle = new Circle(5.0) { Color = "Red" };
            Rectangle rectangle = new Rectangle(4.0, 6.0);
            
            circle.Display();
            circle.Draw();
            circle.Resize(1.5);
            circle.Display();
            
            Console.WriteLine();
            
            rectangle.Display();
            rectangle.Draw();
            
            // Interface references
            Console.WriteLine("\n--- Interface References ---");
            IDrawable[] drawables = { circle, rectangle };
            
            foreach (IDrawable drawable in drawables)
            {
                drawable.Draw();
            }
            
            // Abstract class references
            Console.WriteLine("\n--- Abstract Class References ---");
            List<Shape> shapes = new List<Shape> { circle, rectangle };
            
            foreach (Shape shape in shapes)
            {
                shape.Describe();
                shape.Display();
            }
        }
    }
}

Sealed Classes and Method Hiding

using System;

namespace SealedAndHiding
{
    public class BaseClass
    {
        public virtual void Method1()
        {
            Console.WriteLine("BaseClass.Method1");
        }
        
        public virtual void Method2()
        {
            Console.WriteLine("BaseClass.Method2");
        }
        
        public void Method3()
        {
            Console.WriteLine("BaseClass.Method3");
        }
    }
    
    public class DerivedClass : BaseClass
    {
        // Override - replaces base implementation
        public override void Method1()
        {
            Console.WriteLine("DerivedClass.Method1 (override)");
        }
        
        // New - hides base implementation
        public new void Method2()
        {
            Console.WriteLine("DerivedClass.Method2 (new)");
        }
        
        // Method3 is not virtual, so we can only hide it
        public new void Method3()
        {
            Console.WriteLine("DerivedClass.Method3 (new)");
        }
    }
    
    // Sealed class - cannot be inherited from
    public sealed class SealedClass
    {
        public string Message { get; set; } = "I am sealed!";
        
        public void ShowMessage()
        {
            Console.WriteLine(Message);
        }
    }
    
    // This would cause compile error:
    // public class TryingToInherit : SealedClass { }
    
    public class BaseWithSealedMethod
    {
        public virtual void Method()
        {
            Console.WriteLine("Base method");
        }
    }
    
    public class DerivedWithSealedMethod : BaseWithSealedMethod
    {
        // Sealed override - cannot be overridden further
        public sealed override void Method()
        {
            Console.WriteLine("Derived sealed method");
        }
    }
    
    public class FurtherDerived : DerivedWithSealedMethod
    {
        // This would cause compile error:
        // public override void Method() { }
        
        // But we can hide it with new
        public new void Method()
        {
            Console.WriteLine("Further derived new method");
        }
    }
    
    class Program
    {
        static void Main()
        {
            Console.WriteLine("=== Method Override vs Hiding ===");
            
            BaseClass baseRef = new DerivedClass();
            DerivedClass derivedRef = new DerivedClass();
            
            Console.WriteLine("\nUsing BaseClass reference:");
            baseRef.Method1(); // DerivedClass.Method1 (override)
            baseRef.Method2(); // BaseClass.Method2 (not overridden, just hidden)
            baseRef.Method3(); // BaseClass.Method3 (not virtual)
            
            Console.WriteLine("\nUsing DerivedClass reference:");
            derivedRef.Method1(); // DerivedClass.Method1
            derivedRef.Method2(); // DerivedClass.Method2
            derivedRef.Method3(); // DerivedClass.Method3
            
            Console.WriteLine("\n=== Sealed Class Demo ===");
            SealedClass sealedObj = new SealedClass();
            sealedObj.ShowMessage();
            
            Console.WriteLine("\n=== Sealed Method Demo ===");
            FurtherDerived further = new FurtherDerived();
            further.Method(); // Further derived new method
            
            BaseWithSealedMethod baseRef2 = further;
            baseRef2.Method(); // Derived sealed method
        }
    }
}

Common Pitfalls

  • Forgetting to call base constructor in derived classes
  • Confusing method hiding (new) with overriding (override)
  • Not making base class destructors/virtual methods when needed
  • Overusing inheritance when composition would be better

Polymorphism in C#

Polymorphism allows objects of different types to be treated as objects of a common base type. C# supports compile-time and runtime polymorphism.

Runtime Polymorphism (Virtual Methods)

using System;
using System.Collections.Generic;

namespace RuntimePolymorphism
{
    public abstract class Employee
    {
        public string Name { get; set; }
        public decimal BaseSalary { get; set; }
        
        public Employee(string name, decimal baseSalary)
        {
            Name = name;
            BaseSalary = baseSalary;
        }
        
        // Virtual method - can be overridden
        public virtual decimal CalculateSalary()
        {
            return BaseSalary;
        }
        
        // Abstract method - must be overridden
        public abstract string GetRole();
        
        // Virtual method with default implementation
        public virtual void Work()
        {
            Console.WriteLine($"{Name} is working as an employee");
        }
        
        public void DisplayInfo()
        {
            Console.WriteLine($"{Name} - {GetRole()} - Salary: {CalculateSalary():C}");
        }
    }
    
    public class Developer : Employee
    {
        public string ProgrammingLanguage { get; set; }
        public decimal Bonus { get; set; }
        
        public Developer(string name, decimal baseSalary, string language, decimal bonus) 
            : base(name, baseSalary)
        {
            ProgrammingLanguage = language;
            Bonus = bonus;
        }
        
        // Override virtual method
        public override decimal CalculateSalary()
        {
            return BaseSalary + Bonus;
        }
        
        // Implement abstract method
        public override string GetRole()
        {
            return $"Developer ({ProgrammingLanguage})";
        }
        
        // Override virtual method
        public override void Work()
        {
            Console.WriteLine($"{Name} is writing {ProgrammingLanguage} code");
        }
        
        // New method specific to Developer
        public void DebugCode()
        {
            Console.WriteLine($"{Name} is debugging {ProgrammingLanguage} code");
        }
    }
    
    public class Manager : Employee
    {
        public int TeamSize { get; set; }
        public decimal TeamBonus { get; set; }
        
        public Manager(string name, decimal baseSalary, int teamSize, decimal teamBonus)
            : base(name, baseSalary)
        {
            TeamSize = teamSize;
            TeamBonus = teamBonus;
        }
        
        public override decimal CalculateSalary()
        {
            return BaseSalary + (TeamBonus * TeamSize);
        }
        
        public override string GetRole()
        {
            return $"Manager (Team: {TeamSize})";
        }
        
        public override void Work()
        {
            Console.WriteLine($"{Name} is managing a team of {TeamSize} people");
        }
        
        public void ConductMeeting()
        {
            Console.WriteLine($"{Name} is conducting a team meeting");
        }
    }
    
    public class Intern : Employee
    {
        public int DurationMonths { get; set; }
        
        public Intern(string name, decimal baseSalary, int duration) 
            : base(name, baseSalary)
        {
            DurationMonths = duration;
        }
        
        // Intern doesn't override CalculateSalary - uses base implementation
        
        public override string GetRole()
        {
            return $"Intern ({DurationMonths} months)";
        }
        
        public override void Work()
        {
            Console.WriteLine($"{Name} is learning and assisting team members");
        }
    }
    
    class Program
    {
        static void Main()
        {
            // Create different types of employees
            List<Employee> employees = new List<Employee>
            {
                new Developer("Alice", 60000, "C#", 5000),
                new Manager("Bob", 80000, 5, 2000),
                new Intern("Charlie", 30000, 6),
                new Developer("Diana", 65000, "Python", 6000)
            };
            
            Console.WriteLine("=== Employee Information ===");
            foreach (Employee emp in employees)
            {
                emp.DisplayInfo(); // Runtime polymorphism - calls appropriate methods
            }
            
            Console.WriteLine("\n=== Work Activities ===");
            foreach (Employee emp in employees)
            {
                emp.Work(); // Runtime polymorphism
                
                // Type checking and casting
                if (emp is Developer dev)
                {
                    dev.DebugCode(); // Safe to call after type check
                }
                else if (emp is Manager mgr)
                {
                    mgr.ConductMeeting();
                }
            }
            
            Console.WriteLine("\n=== Salary Report ===");
            decimal totalSalary = 0;
            foreach (Employee emp in employees)
            {
                decimal salary = emp.CalculateSalary(); // Polymorphic call
                totalSalary += salary;
                Console.WriteLine($"{emp.Name}: {salary:C}");
            }
            Console.WriteLine($"Total monthly salary: {totalSalary:C}");
        }
    }
}

Compile-time Polymorphism (Method Overloading)

using System;

namespace CompileTimePolymorphism
{
    public class Calculator
    {
        // Method overloading - same name, different parameters
        
        // Integer addition
        public int Add(int a, int b)
        {
            Console.WriteLine($"Adding integers: {a} + {b}");
            return a + b;
        }
        
        // Double addition
        public double Add(double a, double b)
        {
            Console.WriteLine($"Adding doubles: {a} + {b}");
            return a + b;
        }
        
        // Three parameters
        public int Add(int a, int b, int c)
        {
            Console.WriteLine($"Adding three integers: {a} + {b} + {c}");
            return a + b + c;
        }
        
        // Array of integers
        public int Add(params int[] numbers)
        {
            Console.WriteLine($"Adding {numbers.Length} integers");
            int sum = 0;
            foreach (int num in numbers)
            {
                sum += num;
            }
            return sum;
        }
        
        // String concatenation (different return type and parameters)
        public string Add(string a, string b)
        {
            Console.WriteLine($"Concatenating strings: '{a}' + '{b}'");
            return a + b;
        }
        
        // Generic method
        public T Add<T>(T a, T b) where T : IConvertible
        {
            Console.WriteLine($"Adding generic values: {a} + {b}");
            dynamic da = a;
            dynamic db = b;
            return da + db;
        }
    }
    
    public class MathOperations
    {
        // Operator overloading
        public static Vector operator +(Vector v1, Vector v2)
        {
            return new Vector(v1.X + v2.X, v1.Y + v2.Y);
        }
        
        public static Vector operator *(Vector v, double scalar)
        {
            return new Vector(v.X * scalar, v.Y * scalar);
        }
        
        public static bool operator ==(Vector v1, Vector v2)
        {
            return v1.X == v2.X && v1.Y == v2.Y;
        }
        
        public static bool operator !=(Vector v1, Vector v2)
        {
            return !(v1 == v2);
        }
    }
    
    public class Vector
    {
        public double X { get; set; }
        public double Y { get; set; }
        
        public Vector(double x, double y)
        {
            X = x;
            Y = y;
        }
        
        // Override ToString for better display
        public override string ToString()
        {
            return $"({X}, {Y})";
        }
        
        // Override Equals and GetHashCode when overloading == and !=
        public override bool Equals(object obj)
        {
            return obj is Vector vector && X == vector.X && Y == vector.Y;
        }
        
        public override int GetHashCode()
        {
            return HashCode.Combine(X, Y);
        }
    }
    
    class Program
    {
        static void Main()
        {
            Calculator calc = new Calculator();
            
            Console.WriteLine("=== Method Overloading Examples ===");
            
            // Compile-time polymorphism - compiler chooses appropriate method
            int intResult = calc.Add(5, 3);
            double doubleResult = calc.Add(2.5, 3.7);
            int threeSum = calc.Add(1, 2, 3);
            int arraySum = calc.Add(1, 2, 3, 4, 5);
            string stringResult = calc.Add("Hello", " World");
            decimal genericResult = calc.Add(10.5m, 20.3m);
            
            Console.WriteLine($"Results: {intResult}, {doubleResult}, {threeSum}, {arraySum}, {stringResult}, {genericResult}");
            
            Console.WriteLine("\n=== Operator Overloading Examples ===");
            
            Vector v1 = new Vector(1, 2);
            Vector v2 = new Vector(3, 4);
            
            Vector sum = v1 + v2; // Uses overloaded + operator
            Vector scaled = v1 * 2.5; // Uses overloaded * operator
            
            Console.WriteLine($"v1: {v1}");
            Console.WriteLine($"v2: {v2}");
            Console.WriteLine($"v1 + v2: {sum}");
            Console.WriteLine($"v1 * 2.5: {scaled}");
            Console.WriteLine($"v1 == v2: {v1 == v2}");
            Console.WriteLine($"v1 == new Vector(1, 2): {v1 == new Vector(1, 2)}");
        }
    }
}

Advanced Polymorphism Patterns

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

namespace AdvancedPolymorphism
{
    // Strategy Pattern using polymorphism
    public interface IPaymentStrategy
    {
        void ProcessPayment(decimal amount);
        bool Validate();
    }
    
    public class CreditCardPayment : IPaymentStrategy
    {
        public string CardNumber { get; set; }
        public string ExpiryDate { get; set; }
        
        public CreditCardPayment(string cardNumber, string expiryDate)
        {
            CardNumber = cardNumber;
            ExpiryDate = expiryDate;
        }
        
        public void ProcessPayment(decimal amount)
        {
            Console.WriteLine($"Processing credit card payment of {amount:C} for card {CardNumber}");
            // Simulate processing logic
        }
        
        public bool Validate()
        {
            return !string.IsNullOrEmpty(CardNumber) && CardNumber.Length == 16 &&
                   !string.IsNullOrEmpty(ExpiryDate);
        }
    }
    
    public class PayPalPayment : IPaymentStrategy
    {
        public string Email { get; set; }
        
        public PayPalPayment(string email)
        {
            Email = email;
        }
        
        public void ProcessPayment(decimal amount)
        {
            Console.WriteLine($"Processing PayPal payment of {amount:C} for {Email}");
            // Simulate PayPal processing
        }
        
        public bool Validate()
        {
            return !string.IsNullOrEmpty(Email) && Email.Contains("@");
        }
    }
    
    public class CryptoPayment : IPaymentStrategy
    {
        public string WalletAddress { get; set; }
        
        public CryptoPayment(string walletAddress)
        {
            WalletAddress = walletAddress;
        }
        
        public void ProcessPayment(decimal amount)
        {
            Console.WriteLine($"Processing cryptocurrency payment of {amount:C} to {WalletAddress}");
            // Simulate crypto processing
        }
        
        public bool Validate()
        {
            return !string.IsNullOrEmpty(WalletAddress) && WalletAddress.Length >= 26;
        }
    }
    
    // Factory Pattern using polymorphism
    public static class PaymentFactory
    {
        public static IPaymentStrategy CreatePayment(string type, params string[] parameters)
        {
            return type.ToLower() switch
            {
                "creditcard" when parameters.Length >= 2 => 
                    new CreditCardPayment(parameters[0], parameters[1]),
                "paypal" when parameters.Length >= 1 => 
                    new PayPalPayment(parameters[0]),
                "crypto" when parameters.Length >= 1 => 
                    new CryptoPayment(parameters[0]),
                _ => throw new ArgumentException("Invalid payment type or parameters")
            };
        }
    }
    
    // Template Method Pattern
    public abstract class DataProcessor
    {
        // Template method - defines algorithm skeleton
        public void ProcessData()
        {
            ValidateData();
            TransformData();
            SaveData();
            Cleanup();
        }
        
        protected abstract void ValidateData();
        protected abstract void TransformData();
        protected abstract void SaveData();
        
        // Hook method - can be overridden
        protected virtual void Cleanup()
        {
            Console.WriteLine("Performing default cleanup...");
        }
    }
    
    public class CsvProcessor : DataProcessor
    {
        protected override void ValidateData()
        {
            Console.WriteLine("Validating CSV data...");
        }
        
        protected override void TransformData()
        {
            Console.WriteLine("Transforming CSV data...");
        }
        
        protected override void SaveData()
        {
            Console.WriteLine("Saving CSV data to database...");
        }
        
        protected override void Cleanup()
        {
            Console.WriteLine("Cleaning up temporary CSV files...");
        }
    }
    
    public class JsonProcessor : DataProcessor
    {
        protected override void ValidateData()
        {
            Console.WriteLine("Validating JSON data...");
        }
        
        protected override void TransformData()
        {
            Console.WriteLine("Transforming JSON data...");
        }
        
        protected override void SaveData()
        {
            Console.WriteLine("Saving JSON data to cloud storage...");
        }
        // Uses default Cleanup
    }
    
    class Program
    {
        static void Main()
        {
            Console.WriteLine("=== Strategy Pattern Demo ===");
            
            List<IPaymentStrategy> payments = new List<IPaymentStrategy>
            {
                PaymentFactory.CreatePayment("creditcard", "1234567812345678", "12/25"),
                PaymentFactory.CreatePayment("paypal", "user@example.com"),
                PaymentFactory.CreatePayment("crypto", "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
            };
            
            foreach (var payment in payments.Where(p => p.Validate()))
            {
                payment.ProcessPayment(99.99m);
            }
            
            Console.WriteLine("\n=== Template Method Pattern Demo ===");
            
            DataProcessor csvProcessor = new CsvProcessor();
            DataProcessor jsonProcessor = new JsonProcessor();
            
            Console.WriteLine("Processing CSV data:");
            csvProcessor.ProcessData();
            
            Console.WriteLine("\nProcessing JSON data:");
            jsonProcessor.ProcessData();
            
            Console.WriteLine("\n=== Dynamic Polymorphism with dynamic ===");
            
            dynamic[] objects = { 42, "Hello", 3.14, new List<int> { 1, 2, 3 } };
            
            foreach (dynamic obj in objects)
            {
                TryToAdd(obj);
            }
        }
        
        static void TryToAdd(dynamic obj)
        {
            try
            {
                dynamic result = obj + obj;
                Console.WriteLine($"{obj} + {obj} = {result}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Cannot add {obj} to itself: {ex.Message}");
            }
        }
    }
}

Common Pitfalls

  • Confusing method overloading with overriding
  • Not implementing all abstract methods in concrete classes
  • Forgetting to override Equals and GetHashCode when overloading ==
  • Using dynamic when static typing would be safer

Advanced C# Concepts

Advanced C# features provide powerful tools for writing efficient, safe, and maintainable code including generics, delegates, async/await, and more.

Generics and Constraints

using System;
using System.Collections.Generic;

namespace AdvancedGenerics
{
    // Generic class with multiple type parameters
    public class Repository<TEntity, TKey> where TEntity : class
    {
        private readonly Dictionary<TKey, TEntity> _storage = new Dictionary<TKey, TEntity>();
        
        public void Add(TKey key, TEntity entity)
        {
            _storage[key] = entity;
        }
        
        public TEntity Get(TKey key)
        {
            return _storage.TryGetValue(key, out TEntity entity) ? entity : null;
        }
        
        public IEnumerable<TEntity> GetAll()
        {
            return _storage.Values;
        }
        
        public bool Remove(TKey key)
        {
            return _storage.Remove(key);
        }
        
        // Generic method within generic class
        public TResult Execute<TResult>(Func<TEntity, TResult> operation, TKey key)
        {
            var entity = Get(key);
            return entity != null ? operation(entity) : default(TResult);
        }
    }
    
    // Generic constraints
    public class MathUtils<T> where T : struct, IComparable<T>
    {
        public T Max(T a, T b)
        {
            return a.CompareTo(b) > 0 ? a : b;
        }
        
        public T Min(T a, T b)
        {
            return a.CompareTo(b) < 0 ? a : b;
        }
        
        public T[] CreateRange(T start, T end, int count)
        {
            // This is simplified - in real code you'd need more complex logic
            dynamic dStart = start;
            dynamic dEnd = end;
            dynamic step = (dEnd - dStart) / (count - 1);
            
            T[] result = new T[count];
            for (int i = 0; i < count; i++)
            {
                result[i] = dStart + (step * i);
            }
            return result;
        }
    }
    
    // Covariant interface
    public interface IReadOnlyRepository<out T>
    {
        T GetById(int id);
        IEnumerable<T> GetAll();
    }
    
    // Contravariant interface
    public interface IWriter<in T>
    {
        void Write(T item);
    }
    
    // Generic delegate
    public delegate T Transformer<T>(T input);
    
    public static class GenericExtensions
    {
        // Generic extension method
        public static string ToString<T>(this IEnumerable<T> collection, string separator)
        {
            return string.Join(separator, collection);
        }
        
        // Generic method with multiple constraints
        public static TResult Convert<TSource, TResult>(TSource source)
            where TSource : IConvertible
            where TResult : IConvertible
        {
            return (TResult)System.Convert.ChangeType(source, typeof(TResult));
        }
    }
    
    class Program
    {
        static void Main()
        {
            Console.WriteLine("=== Generic Repository Demo ===");
            
            var userRepo = new Repository<User, int>();
            userRepo.Add(1, new User("Alice", 25));
            userRepo.Add(2, new User("Bob", 30));
            
            var user = userRepo.Get(1);
            Console.WriteLine($"User 1: {user}");
            
            string name = userRepo.Execute(u => u.Name, 2);
            Console.WriteLine($"User 2 name: {name}");
            
            Console.WriteLine("\n=== Generic Math Demo ===");
            
            var intMath = new MathUtils<int>();
            Console.WriteLine($"Max of 5 and 10: {intMath.Max(5, 10)}");
            
            var doubleMath = new MathUtils<double>();
            Console.WriteLine($"Min of 3.14 and 2.71: {doubleMath.Min(3.14, 2.71)}");
            
            Console.WriteLine("\n=== Generic Extension Methods ===");
            
            List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
            string joined = numbers.ToString(" | ");
            Console.WriteLine($"Joined numbers: {joined}");
            
            double converted = GenericExtensions.Convert<int, double>(42);
            Console.WriteLine($"Converted 42 to double: {converted}");
        }
    }
    
    public class User
    {
        public string Name { get; set; }
        public int Age { get; set; }
        
        public User(string name, int age)
        {
            Name = name;
            Age = age;
        }
        
        public override string ToString()
        {
            return $"{Name} ({Age})";
        }
    }
}

Delegates, Events, and Lambda Expressions

using System;
using System.Threading.Tasks;

namespace DelegatesAndEvents
{
    // Delegate definitions
    public delegate void SimpleDelegate();
    public delegate int CalculatorDelegate(int a, int b);
    public delegate bool FilterDelegate<T>(T item);
    
    public class EventPublisher
    {
        // Event with built-in EventHandler delegate
        public event EventHandler<MessageEventArgs> MessageSent;
        
        // Custom delegate event
        public event Action<string> SimpleMessage;
        
        public void SendMessage(string message)
        {
            Console.WriteLine($"Sending: {message}");
            
            // Raise events
            SimpleMessage?.Invoke(message);
            MessageSent?.Invoke(this, new MessageEventArgs(message));
        }
    }
    
    public class MessageEventArgs : EventArgs
    {
        public string Message { get; }
        
        public MessageEventArgs(string message)
        {
            Message = message;
        }
    }
    
    public class EventSubscriber
    {
        public EventSubscriber(EventPublisher publisher)
        {
            // Subscribe to events
            publisher.MessageSent += OnMessageSent;
            publisher.SimpleMessage += OnSimpleMessage;
        }
        
        private void OnMessageSent(object sender, MessageEventArgs e)
        {
            Console.WriteLine($"Received via event: {e.Message}");
        }
        
        private void OnSimpleMessage(string message)
        {
            Console.WriteLine($"Received via action: {message}");
        }
    }
    
    public class DelegateDemo
    {
        public void DemonstrateDelegates()
        {
            Console.WriteLine("=== Delegate Examples ===");
            
            // Method group conversion
            SimpleDelegate simple = SayHello;
            simple += SayGoodbye;
            simple(); // Calls both methods
            
            // Lambda expressions
            CalculatorDelegate add = (a, b) => a + b;
            CalculatorDelegate multiply = (a, b) => a * b;
            
            Console.WriteLine($"Add: {add(5, 3)}");
            Console.WriteLine($"Multiply: {multiply(5, 3)}");
            
            // Func and Action delegates (built-in)
            Func<int, int, int> subtract = (a, b) => a - b;
            Action<string> print = message => Console.WriteLine(message);
            
            Console.WriteLine($"Subtract: {subtract(10, 4)}");
            print("Hello from Action!");
            
            // Predicate delegate
            Predicate<int> isEven = x => x % 2 == 0;
            Console.WriteLine($"Is 4 even? {isEven(4)}");
            
            // Chaining delegates
            SimpleDelegate chain = null;
            chain += () => Console.WriteLine("First");
            chain += () => Console.WriteLine("Second");
            chain += () => Console.WriteLine("Third");
            
            Console.WriteLine("Delegate chain:");
            chain();
        }
        
        public void DemonstrateEvents()
        {
            Console.WriteLine("\n=== Event Examples ===");
            
            EventPublisher publisher = new EventPublisher();
            EventSubscriber subscriber = new EventSubscriber(publisher);
            
            publisher.SendMessage("Hello Event World!");
            publisher.SendMessage("Another message");
        }
        
        public void DemonstrateLambdaAdvanced()
        {
            Console.WriteLine("\n=== Advanced Lambda Examples ===");
            
            // Closure example
            int factor = 2;
            Func<int, int> multiplier = x => x * factor;
            
            Console.WriteLine($"Multiplier (factor={factor}): {multiplier(5)}");
            
            factor = 3; // The lambda captures the variable, not the value
            Console.WriteLine($"Multiplier (factor={factor}): {multiplier(5)}");
            
            // Expression-bodied members with lambdas
            var processor = new DataProcessor();
            processor.ProcessData(5, x => x * x, result => Console.WriteLine($"Result: {result}"));
            
            // Async lambda
            Task.Run(async () =>
            {
                await Task.Delay(1000);
                Console.WriteLine("Async lambda completed after 1 second");
            }).Wait();
        }
        
        private void SayHello()
        {
            Console.WriteLine("Hello!");
        }
        
        private void SayGoodbye()
        {
            Console.WriteLine("Goodbye!");
        }
    }
    
    public class DataProcessor
    {
        public void ProcessData(int input, Func<int, int> transform, Action<int> output)
        {
            int result = transform(input);
            output(result);
        }
    }
    
    class Program
    {
        static void Main()
        {
            DelegateDemo demo = new DelegateDemo();
            demo.DemonstrateDelegates();
            demo.DemonstrateEvents();
            demo.DemonstrateLambdaAdvanced();
        }
    }
}

Async/Await and Modern C# Features

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

namespace ModernCSharpFeatures
{
    public class AsyncDemo
    {
        public async Task DemonstrateAsyncAwait()
        {
            Console.WriteLine("=== Async/Await Demo ===");
            
            try
            {
                // Basic async operation
                string data = await ReadFileAsync("example.txt");
                Console.WriteLine($"File content: {data}");
                
                // Multiple async operations
                Task<string> task1 = ReadFileAsync("file1.txt");
                Task<string> task2 = ReadFileAsync("file2.txt");
                
                string[] results = await Task.WhenAll(task1, task2);
                Console.WriteLine($"Combined results: {string.Join(" | ", results)}");
                
                // Async with cancellation
                using var cancellationSource = new System.Threading.CancellationTokenSource();
                cancellationSource.CancelAfter(TimeSpan.FromSeconds(2));
                
                await LongRunningOperationAsync(cancellationSource.Token);
            }
            catch (FileNotFoundException ex)
            {
                Console.WriteLine($"File not found: {ex.FileName}");
            }
            catch (TaskCanceledException)
            {
                Console.WriteLine("Operation was cancelled");
            }
        }
        
        private async Task<string> ReadFileAsync(string filename)
        {
            // Simulate async file read
            await Task.Delay(1000);
            
            if (!File.Exists(filename))
                throw new FileNotFoundException("File not found", filename);
                
            return $"Content of {filename}";
        }
        
        private async Task LongRunningOperationAsync(System.Threading.CancellationToken cancellationToken)
        {
            for (int i = 0; i < 10; i++)
            {
                cancellationToken.ThrowIfCancellationRequested();
                Console.WriteLine($"Working... {i + 1}/10");
                await Task.Delay(500);
            }
        }
    }
    
    public class ModernFeaturesDemo
    {
        public void DemonstrateRecords()
        {
            Console.WriteLine("\n=== Records Demo ===");
            
            // Record types (C# 9.0+)
            var person1 = new Person("Alice", 25);
            var person2 = new Person("Alice", 25);
            var person3 = person1 with { Age = 26 };
            
            Console.WriteLine($"person1: {person1}");
            Console.WriteLine($"person2: {person2}");
            Console.WriteLine($"person3: {person3}");
            Console.WriteLine($"person1 == person2: {person1 == person2}"); // True - value equality
            Console.WriteLine($"person1 == person3: {person1 == person3}"); // False
            
            // Deconstruction
            var (name, age) = person1;
            Console.WriteLine($"Deconstructed: Name={name}, Age={age}");
        }
        
        public void DemonstratePatternMatching()
        {
            Console.WriteLine("\n=== Pattern Matching Demo ===");
            
            object[] objects = { 42, "Hello", 3.14, null, new List<int>(), "World" };
            
            foreach (object obj in objects)
            {
                string description = obj switch
                {
                    int i when i > 50 => $"Large integer: {i}",
                    int i => $"Small integer: {i}",
                    string s when s.Length > 5 => $"Long string: {s}",
                    string s => $"Short string: {s}",
                    IEnumerable<int> list => $"List with {System.Linq.Enumerable.Count(list)} items",
                    null => "Null object",
                    _ => $"Unknown type: {obj.GetType().Name}"
                };
                
                Console.WriteLine(description);
            }
        }
        
        public void DemonstrateNullableReferenceTypes()
        {
            Console.WriteLine("\n=== Nullable Reference Types Demo ===");
            
            string nonNullable = "Hello"; // Can't be null (with warnings enabled)
            string? nullable = null;     // Explicitly nullable
            
            // Safe usage
            if (nullable != null)
            {
                Console.WriteLine(nullable.Length); // No warning
            }
            
            // Null-forgiving operator (use carefully!)
            Console.WriteLine(nullable!.Length); // I know what I'm doing!
            
            // Null-coalescing
            string safe = nullable ?? "Default";
            Console.WriteLine($"Safe value: {safe}");
        }
        
        public void DemonstrateRangesAndIndices()
        {
            Console.WriteLine("\n=== Ranges and Indices Demo ===");
            
            int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            
            Index first = 0;
            Index last = ^1; // From end
            
            Console.WriteLine($"First: {numbers[first]}, Last: {numbers[last]}");
            
            Range range = 1..4; // Items 1, 2, 3
            Range fromStart = ..3; // Items 0, 1, 2
            Range toEnd = 7..; // Items 7, 8, 9
            Range all = ..; // All items
            
            Console.WriteLine($"Range 1..4: {string.Join(", ", numbers[range])}");
            Console.WriteLine($"Range ..3: {string.Join(", ", numbers[fromStart])}");
            Console.WriteLine($"Range 7..: {string.Join(", ", numbers[toEnd])}");
        }
    }
    
    // Record type (immutable by default)
    public record Person(string Name, int Age);
    
    // Record with additional methods
    public record Employee(string Name, int Age, string Department) : Person(Name, Age)
    {
        public void Work() => Console.WriteLine($"{Name} is working in {Department}");
    }
    
    class Program
    {
        static async Task Main(string[] args)
        {
            var asyncDemo = new AsyncDemo();
            await asyncDemo.DemonstrateAsyncAwait();
            
            var modernDemo = new ModernFeaturesDemo();
            modernDemo.DemonstrateRecords();
            modernDemo.DemonstratePatternMatching();
            modernDemo.DemonstrateNullableReferenceTypes();
            modernDemo.DemonstrateRangesAndIndices();
        }
    }
}

Common Pitfalls

  • Forgetting to use await on async methods
  • Deadlocks when mixing async and synchronous code
  • Not properly handling exceptions in async methods
  • Overusing advanced features when simpler solutions exist