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
-
Windows
Download and install the .NET SDK from the official Microsoft website. Alternatively, install Visual Studio which includes the .NET SDK. -
macOS
Download the .NET SDK for macOS from the official Microsoft website or use Homebrew:brew install --cask dotnet-sdk -
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
-
Create a new console application:
dotnet new console -o HelloWorld cd HelloWorld -
Open the
Program.csfile and examine its content:// See https://aka.ms/new-console-template for more information Console.WriteLine("Hello, World!"); -
Run the program:
dotnet runYou 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.WriteLinefor output with newline - Use
Console.Writefor 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 / 2equals2, not2.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 = cmeansa = (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.Parsethrows 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
StringBuilderfor 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 - useStringBuilderinstead - Forgetting that strings are case-sensitive by default
- Not using
StringComparisonfor 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.Resizecreates 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
Removeonly 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
fixedstatement 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
stringis 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
breakwhencontinueis more appropriate - Overusing
gotocreates "spaghetti code" breakonly exits the innermost loop- Placing code after
continuethat 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
breakin 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
BlockingCollectionproperly - Using
ConcurrentBagwhen 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 ofFirstOrDefault() - 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
EqualsandGetHashCodewhen overloading== - Using
dynamicwhen 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
awaiton async methods - Deadlocks when mixing async and synchronous code
- Not properly handling exceptions in async methods
- Overusing advanced features when simpler solutions exist