C# (pronounced "C-sharp") is a modern, object-oriented programming language developed by Microsoft in the early 2000s. It was designed for building Windows applications on the .NET platform. C# combines the power of C++ with the ease of Visual Basic, and it supports strong type checking, garbage collection, and rich class libraries.
To start coding in C#, you can install either Visual Studio (a fully-featured IDE) or Visual Studio Code (a lightweight editor with extensions). Visual Studio includes built-in tools for designing, coding, and debugging. Visual Studio Code requires installing the C# extension and the .NET SDK separately.
The classic first program outputs "Hello, World!" to the console. It demonstrates the structure of a basic C# program.
The Main method is the entry point of every C# console application. When the program runs, execution starts inside Main.
Variables store data and must be declared with a specific data type like int, string, or bool. C# is statically typed, so the type of variables is checked at compile time.
Console.WriteLine() prints output to the console. Console.ReadLine() reads user input from the console.
Comments explain code and are ignored during execution. Use // for single-line comments and /* ... */ for multi-line. Proper code style improves readability and maintainability.
Use PascalCase for class names and method names. Use camelCase for local variables and parameters. Follow these conventions to keep code consistent and clear.
.NET is a framework that provides tools and libraries to build apps. The Common Language Runtime (CLR) executes C# programs, handling memory management and security.
You can run C# programs inside your IDE or from the command line using the dotnet CLI. Debugging tools let you step through code, inspect variables, and find errors.
// This is a simple C# program demonstrating key concepts using System; // Import the System namespace for Console class namespace IntroductionToCSharp // Namespace to organize code { // Program class - contains the Main method, entry point of the program class Program { // Main method - program execution starts here static void Main(string[] args) { // Declare a string variable and assign a value string greeting = "Hello, World!"; // Output the greeting to the console Console.WriteLine(greeting); // Declare variables of different data types int year = 2025; // Integer double version = 10.0; // Double (floating point) bool isActive = true; // Boolean // Output variables with labels Console.WriteLine("Year: " + year); Console.WriteLine("C# Version: " + version); Console.WriteLine("Is Active: " + isActive); // Ask user for input Console.Write("Enter your name: "); // Read input from the user and store in variable string userName = Console.ReadLine(); // Output personalized greeting Console.WriteLine("Welcome, " + userName + "!"); // Single line comment example /* Multi-line comment example explaining the next line of code */ Console.WriteLine("Press any key to exit..."); // Wait for user to press a key before closing Console.ReadKey(); } } }
Conditional statements allow the program to make decisions and execute different blocks of code based on boolean conditions.
The if
statement executes its block when the condition is true.
The else if
statement checks another condition if the previous if
or else if
was false.
The else
block runs if none of the previous conditions are true.
The switch
statement selects a code block to execute from many options based on the value of an expression.
It is a cleaner alternative to multiple if-else
when checking a variable against multiple constant values.
A for
loop repeats a block of code a set number of times.
It consists of an initializer, a condition, and an iterator.
It is useful when the number of iterations is known beforehand.
A while
loop executes its body repeatedly as long as the specified condition remains true.
The condition is checked before each iteration.
A do-while
loop executes its body at least once, then repeats the loop while the condition is true.
The condition is checked after the loop body.
break
immediately exits the nearest enclosing loop or switch statement.
continue
skips the rest of the current iteration and moves to the next iteration of the loop.
Nested loops are loops inside loops. They are commonly used to process multi-dimensional data structures such as matrices.
The goto
statement jumps to a labeled point in code.
Its use is generally discouraged as it makes code harder to read and maintain, causing “spaghetti code.”
It is sometimes used in rare cases like error handling.
Logical operators &&
(AND) and ||
(OR) perform short-circuit evaluation,
meaning they stop evaluating as soon as the result is determined,
improving performance and avoiding unnecessary computation.
Keep conditions clear and simple.
Avoid deeply nested code by refactoring into methods.
Prefer switch
over long if-else
chains.
Avoid goto
except for exceptional cases.
// Demonstrating control flow and loops in C# using System; namespace ControlFlowExample { class Program { static void Main(string[] args) { // if, else if, else example int score = 75; if (score >= 90) { Console.WriteLine("Grade: A"); } else if (score >= 80) { Console.WriteLine("Grade: B"); } else if (score >= 70) { Console.WriteLine("Grade: C"); } else { Console.WriteLine("Grade: F"); } // switch statement example int day = 2; switch (day) { case 1: Console.WriteLine("Monday"); break; case 2: Console.WriteLine("Tuesday"); break; case 3: Console.WriteLine("Wednesday"); break; default: Console.WriteLine("Another day"); break; } // for loop example Console.WriteLine("For loop 1 to 5:"); for (int i = 1; i <= 5; i++) { Console.Write(i + " "); } Console.WriteLine(); // while loop example Console.WriteLine("While loop counting down:"); int count = 3; while (count > 0) { Console.Write(count + " "); count--; } Console.WriteLine(); // do-while loop example Console.WriteLine("Do-while loop running at least once:"); int number = 0; do { Console.WriteLine("Number is " + number); number++; } while (number < 2); // break and continue example Console.WriteLine("Break and continue:"); for (int j = 1; j <= 5; j++) { if (j == 4) { Console.WriteLine("Breaking at 4"); break; // exit loop when j == 4 } if (j % 2 == 0) { continue; // skip even numbers } Console.WriteLine("Odd number: " + j); } // nested loops example Console.WriteLine("Nested loops - multiplication table 1 to 3:"); for (int row = 1; row <= 3; row++) { for (int col = 1; col <= 3; col++) { Console.Write((row * col) + "\t"); } Console.WriteLine(); } // goto example (avoid if possible) Console.WriteLine("Goto demonstration:"); int x = 0; start: if (x < 3) { Console.WriteLine("x is " + x); x++; goto start; // jump back to start label } // short-circuit evaluation example Console.WriteLine("Short-circuit evaluation:"); bool a = false; bool b = true; if (a && MethodNotCalled()) { Console.WriteLine("Won't print - a is false"); } if (b || MethodNotCalled()) { Console.WriteLine("Prints - b is true, skips second condition"); } } static bool MethodNotCalled() { Console.WriteLine("This method won't be called due to short-circuit"); return true; } } }
In C#, a method is a block of code that performs a task and is declared using the returnType methodName(parameters)
format.
Methods are used to promote reusability and better structure your code.
Parameters are defined in the method signature and act as input variables. Arguments are the actual values passed to the method when it's called. C# supports passing parameters by value and reference.
A method’s return type defines the type of value it returns.
If a method doesn’t return anything, it is declared with void
.
Method overloading allows multiple methods with the same name but different parameter lists. The compiler selects the correct method based on the passed arguments.
Optional parameters let you define default values in the method signature. Named parameters allow arguments to be passed by specifying their names, which improves clarity.
Recursive methods are functions that call themselves. They must include a base condition to avoid infinite loops and stack overflows.
By default, C# passes arguments by value. To allow a method to modify the original variable, you can use the ref
or out
keywords.
ref
requires the variable to be initialized before passing, while out
does not.
Local functions are declared inside another method and are only accessible within that method. They help encapsulate logic that shouldn't be exposed outside.
Lambda expressions are a concise way to write inline functions using the =>
operator.
Anonymous methods use the delegate
keyword and don’t require a name.
Expression-bodied members allow single-line methods or properties using =>
instead of full block syntax,
making code shorter and cleaner when appropriate.
// Full demonstration of methods and functions in C# using System; namespace MethodExamples { class Program { static void Main(string[] args) { // Calling a void method GreetUser(); // Calling method with return value int result = Add(4, 6); Console.WriteLine("4 + 6 = " + result); // Method overloading Console.WriteLine("Overload 1: " + Multiply(3, 2)); // 6 Console.WriteLine("Overload 2: " + Multiply(3.5, 2)); // 7.0 // Using optional and named parameters DisplayInfo("Alice"); // uses default age DisplayInfo("Bob", age: 25); // overrides age // Recursive method: factorial Console.WriteLine("Factorial of 4: " + Factorial(4)); // Passing by reference int number = 10; DoubleValue(ref number); Console.WriteLine("Doubled number: " + number); // Using out string input = "123"; bool isParsed = TryParseNumber(input, out int parsed); Console.WriteLine("Parsed: " + isParsed + ", Value: " + parsed); // Local function int Square(int x) => x * x; Console.WriteLine("Square of 5: " + Square(5)); // Lambda expression Funcsubtract = (a, b) => a - b; Console.WriteLine("10 - 4 = " + subtract(10, 4)); // Expression-bodied method Console.WriteLine("Max of 9 and 3 = " + Max(9, 3)); } // Method without return value static void GreetUser() { Console.WriteLine("Welcome to C# Methods!"); } // Method with return type and parameters static int Add(int a, int b) { return a + b; } // Method overloading static int Multiply(int x, int y) { return x * y; } static double Multiply(double x, int y) { return x * y; } // Method with optional and named parameters static void DisplayInfo(string name, int age = 18) { Console.WriteLine($"Name: {name}, Age: {age}"); } // Recursive method to calculate factorial static int Factorial(int n) { if (n == 0 || n == 1) return 1; return n * Factorial(n - 1); } // Pass-by-reference using ref static void DoubleValue(ref int num) { num *= 2; } // Out parameter static bool TryParseNumber(string input, out int number) { return int.TryParse(input, out number); } // Expression-bodied member static int Max(int a, int b) => (a > b) ? a : b; } }
A class is a blueprint for creating objects. It defines properties and behaviors through fields and methods.
An object is an instance of a class, created using the new
keyword.
Fields are variables declared in a class to hold data. Properties provide controlled access to those fields, often using get/set accessors.
A constructor is a special method that runs when an object is created, initializing its state. A destructor (~ClassName) runs when the object is destroyed, used to free resources.
Overloading allows methods with the same name but different parameters.
Overriding lets a subclass change the behavior of a method defined in a base class, using the override
keyword.
Encapsulation hides internal details of an object. Access modifiers like public
, private
, and protected
control visibility.
The this
keyword refers to the current instance of the class. It is used to resolve naming conflicts or refer to instance members.
Static members belong to the class rather than any instance. A static class cannot be instantiated and is often used for utility functions.
C# allows initializing objects using object initializer syntax with curly braces to assign property values at creation.
Inheritance lets one class (child) acquire properties and methods from another (parent). It promotes code reuse and organization.
Polymorphism lets you treat derived class objects as base class objects. This enables methods to be called on objects of different types in a unified way.
// Full demonstration of OOP basics in C# using System; namespace OOPBasics { // Base class Animal class Animal { // Field private string name; // Property public string Name { get { return name; } set { name = value; } } // Constructor public Animal(string name) { this.name = name; } // Virtual method to be overridden public virtual void Speak() { Console.WriteLine("Animal sound"); } // Destructor (rarely used) ~Animal() { Console.WriteLine("Animal destroyed"); } } // Derived class Dog class Dog : Animal { // Constructor calling base constructor public Dog(string name) : base(name) { } // Overriding Speak method public override void Speak() { Console.WriteLine(Name + " says Woof!"); } } // Static class for utility static class MathHelper { public static int Square(int x) { return x * x; } } class Program { static void Main(string[] args) { // Creating object using object initializer Animal cat = new Animal("Whiskers"); cat.Speak(); // Creating a Dog object (polymorphism) Animal myDog = new Dog("Buddy"); myDog.Speak(); // Calls overridden method // Accessing static method Console.WriteLine("Square of 4: " + MathHelper.Square(4)); // Using this keyword (demonstrated in constructor above) Dog anotherDog = new Dog("Max"); anotherDog.Speak(); // Example of object initializer syntax Animal rabbit = new Animal(name: "Fluffy"); Console.WriteLine("Rabbit's name: " + rabbit.Name); } } }
An abstract class cannot be instantiated and may contain abstract methods that must be implemented by derived classes. It serves as a base for other classes.
Interfaces define contracts with method signatures but no implementation. A class can implement multiple interfaces, promoting flexibility and decoupling.
A sealed class cannot be inherited. A sealed method prevents further overriding in derived classes. This is useful for securing functionality.
A virtual method can be overridden in derived classes using the override
keyword,
allowing custom behavior while preserving polymorphism.
When a derived class defines a method with the same name as one in its base class, the new
keyword hides the base version.
is
checks if an object is a specific type.
as
tries to cast and returns null if it fails.
typeof
returns the Type
of a class or variable.
Extension methods allow you to "add" methods to existing types without modifying their source code.
They're declared in static classes using this
in the parameter list.
Partial classes and methods allow a class or method to be split across multiple files. Useful in large projects and auto-generated code (e.g., WinForms, Entity Framework).
A class, struct, or enum can be nested inside another class. Useful for logically grouping code and limiting scope.
Design patterns are proven solutions to common software design problems. Examples include Singleton, Factory, Observer, and Strategy. They improve code structure, readability, and reusability.
// Demonstrating advanced OOP concepts in C# using System; namespace AdvancedOOP { // Abstract class and method abstract class Shape { public abstract double GetArea(); } // Interface example interface IPrintable { void Print(); } // Derived class implementing interface and abstract class class Circle : Shape, IPrintable { public double Radius { get; set; } public Circle(double radius) { Radius = radius; } // Overriding abstract method public override double GetArea() { return Math.PI * Radius * Radius; } // Implementing interface method public void Print() { Console.WriteLine("Circle with radius " + Radius); } } // Sealed class - cannot be inherited sealed class Logger { public void Log(string message) { Console.WriteLine("[Log]: " + message); } } // Base and derived classes demonstrating virtual, override, new class Animal { public virtual void Speak() => Console.WriteLine("Animal sound"); public void Eat() => Console.WriteLine("Animal eats"); } class Dog : Animal { // Overriding base method public override void Speak() => Console.WriteLine("Dog barks"); // Hiding base method public new void Eat() => Console.WriteLine("Dog eats"); } // Extension method must be in static class static class StringExtensions { public static int WordCount(this string str) { return str.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length; } } // Partial class - part 1 partial class Product { public string Name { get; set; } partial void OnCreated(); public Product(string name) { Name = name; OnCreated(); } } // Partial class - part 2 partial class Product { partial void OnCreated() { Console.WriteLine("Product created: " + Name); } } class OuterClass { public class NestedClass { public void Display() => Console.WriteLine("Inside Nested Class"); } } class Program { static void Main() { // Abstract and interface example Circle c = new Circle(5); Console.WriteLine("Area: " + c.GetArea()); c.Print(); // Sealed class usage Logger log = new Logger(); log.Log("System started"); // Virtual, override, and new keyword Animal a = new Dog(); // Polymorphism a.Speak(); // Dog barks a.Eat(); // Animal eats (Eat is not virtual) Dog d = new Dog(); d.Eat(); // Dog eats (method hiding) // Type checking and casting if (a is Dog) { Dog casted = a as Dog; Console.WriteLine("Casted to Dog successfully: " + (casted != null)); } Console.WriteLine("Type of Circle: " + typeof(Circle).Name); // Extension method usage string sentence = "Hello world from C#"; Console.WriteLine("Word Count: " + sentence.WordCount()); // Partial class Product p = new Product("Laptop"); // Nested class OuterClass.NestedClass nested = new OuterClass.NestedClass(); nested.Display(); } } }
Arrays store fixed-size collections of elements of the same type. Multidimensional arrays (e.g., 2D arrays) are useful for matrix-like data.
List<T>
is a resizable, generic collection for storing elements.
It's more flexible than arrays and supports dynamic resizing.
A Dictionary
stores key-value pairs and allows fast lookups by key.
A HashSet<T>
represents a collection of unique values with no duplicates.
Queue
is FIFO (first-in-first-out), while Stack
is LIFO (last-in-first-out).
Generics allow methods and classes to operate on data types specified at runtime, promoting reusability and type safety.
IEnumerable
allows iteration over a collection. IEnumerator
exposes the current element and supports manual iteration.
LINQ (Language Integrated Query) allows querying collections using a SQL-like syntax or lambda expressions for filtering, sorting, and projecting data.
These collections ensure data cannot be modified once created, improving safety and predictability in concurrent or shared contexts.
Choose the right collection based on access patterns. Lists are fast for indexing, dictionaries for key lookup, sets for uniqueness, and arrays for performance-critical operations.
// Demonstrating collections and generics in C# using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; namespace CollectionsGenerics { class Program { // Generic method static void PrintItems<T>(List<T> items) { foreach (T item in items) { Console.Write(item + " "); } Console.WriteLine(); } static void Main() { // Arrays and Multidimensional Arrays int[] numbers = { 1, 2, 3 }; int[,] matrix = { { 1, 2 }, { 3, 4 } }; Console.WriteLine("Array:"); foreach (var num in numbers) Console.Write(num + " "); Console.WriteLine(); Console.WriteLine("2D Array:"); for (int i = 0; i < matrix.GetLength(0); i++) { for (int j = 0; j < matrix.GetLength(1); j++) { Console.Write(matrix[i, j] + " "); } Console.WriteLine(); } // List<T> List<string> fruits = new List<string> { "Apple", "Banana", "Cherry" }; fruits.Add("Date"); PrintItems(fruits); // Dictionary<TKey, TValue> Dictionary<string, int> ages = new Dictionary<string, int> { { "Alice", 30 }, { "Bob", 25 } }; Console.WriteLine("Bob's age: " + ages["Bob"]); // HashSet<T> HashSet<int> uniqueNums = new HashSet<int> { 1, 2, 2, 3 }; Console.WriteLine("HashSet contents:"); foreach (var n in uniqueNums) Console.Write(n + " "); Console.WriteLine(); // Queue Queue<string> queue = new Queue<string>(); queue.Enqueue("First"); queue.Enqueue("Second"); Console.WriteLine("Dequeued: " + queue.Dequeue()); // Stack Stack<string> stack = new Stack<string>(); stack.Push("Top"); stack.Push("Bottom"); Console.WriteLine("Popped: " + stack.Pop()); // IEnumerable & IEnumerator IEnumerable<int> ieNums = new List<int> { 10, 20, 30 }; IEnumerator<int> enumerator = ieNums.GetEnumerator(); while (enumerator.MoveNext()) { Console.WriteLine("Enumerated: " + enumerator.Current); } // LINQ Basics var evenNumbers = numbers.Where(n => n % 2 == 0).ToList(); Console.WriteLine("Even numbers:"); evenNumbers.ForEach(n => Console.WriteLine(n)); // ReadOnlyCollection ReadOnlyCollection<string> readOnlyFruits = new ReadOnlyCollection<string>(fruits); Console.WriteLine("First read-only fruit: " + readOnlyFruits[0]); // Performance tips Console.WriteLine("Capacity of fruit list: " + fruits.Capacity); } } }
Exceptions are unexpected errors that occur during runtime. C# provides structured handling to catch and recover from exceptions without crashing the program.
The try
block encloses code that might throw exceptions.
The catch
block handles specific or general exceptions.
The finally
block runs regardless of whether an exception was thrown, usually used for cleanup.
You can define your own exception types by inheriting from Exception
. This adds semantic clarity and custom behavior.
Use the throw
keyword to manually raise an exception. This is useful for validation or enforcing business rules.
The using
statement ensures that disposable resources (like files or streams) are released automatically even if an exception occurs.
Exception filters let you conditionally catch exceptions using when
clauses, enabling fine-grained control over exception handling.
A stack trace shows the sequence of method calls that led to the exception, helping developers trace the error origin.
Catch only expected exceptions, avoid swallowing exceptions, always log errors, and prefer specific exception types.
You can handle different exceptions in separate catch
blocks or use a single block for general exceptions when appropriate.
Logging exceptions helps with diagnostics and post-mortem analysis. Use tools like Console
, Trace
, or logging frameworks.
// Full C# demonstration of exception handling concepts using System; using System.IO; namespace ExceptionHandlingDemo { // Custom exception class public class AgeException : Exception { public AgeException(string message) : base(message) { } } class Program { static void Main() { try { // Throwing and catching a custom exception int age = -5; if (age < 0) throw new AgeException("Age cannot be negative"); // Try to open a file that may not exist using (StreamReader reader = new StreamReader("nonexistent.txt")) { string content = reader.ReadToEnd(); Console.WriteLine(content); } } catch (AgeException ex) { Console.WriteLine("Custom Error: " + ex.Message); } catch (FileNotFoundException ex) when (ex.Message.Contains("nonexistent")) { // Using exception filter Console.WriteLine("Filtered File Error: File does not exist."); } catch (Exception ex) { // General catch block Console.WriteLine("General Error: " + ex.Message); Console.WriteLine("Stack Trace: " + ex.StackTrace); } finally { // Always executes Console.WriteLine("Execution finished. Resources cleaned."); } // Handling multiple exceptions try { string input = "abc"; int number = int.Parse(input); // FormatException } catch (FormatException) { Console.WriteLine("Input was not a valid number."); } catch (OverflowException) { Console.WriteLine("Input was too large."); } // Logging an exception (simple version) try { int[] arr = { 1, 2, 3 }; Console.WriteLine(arr[5]); // IndexOutOfRangeException } catch (Exception ex) { Console.WriteLine("Logging error: " + ex.Message); // In real-world apps, use logging frameworks here } } } }
Strings in C# are declared using the string
keyword and initialized with double quotes.
They are reference types and are stored on the heap.
Strings are immutable in C#, meaning their values cannot be changed after creation. Modifying a string creates a new object in memory.
Strings come with built-in methods like Substring
, Replace
, IndexOf
, ToUpper
, etc., for manipulation and searching.
Interpolation (using $"{value}"
) and formatting (like String.Format
) make string construction dynamic and readable.
StringBuilder
is used for building dynamic strings efficiently, especially in loops, avoiding the cost of string immutability.
Regular expressions (regex) help search and manipulate strings using patterns.
The Regex
class provides powerful tools for matching.
Strings can be parsed into numeric types using int.Parse
, TryParse
, or Convert.ToInt32
.
Comparing strings can be case-sensitive or culture-aware using String.Compare
, .Equals()
, or CultureInfo
.
Span<T>
and Memory<T>
offer advanced, high-performance memory manipulation without allocations.
They're used in performance-critical string slicing.
Localization involves using resource (.resx) files to support multiple languages. This enables internationalization of strings in an application.
// Demonstrating string operations in C# using System; using System.Globalization; using System.Text; using System.Text.RegularExpressions; namespace StringDemo { class Program { static void Main() { // Declaration and Initialization string greeting = "Hello World"; Console.WriteLine("Original: " + greeting); // Immutability string changed = greeting.Replace("World", "C#"); Console.WriteLine("Changed: " + changed); Console.WriteLine("Original still: " + greeting); // unchanged // String methods Console.WriteLine("Substring(0,5): " + greeting.Substring(0, 5)); Console.WriteLine("IndexOf('o'): " + greeting.IndexOf('o')); Console.WriteLine("ToUpper(): " + greeting.ToUpper()); // String interpolation and formatting string name = "Alice"; int score = 95; Console.WriteLine($"Name: {name}, Score: {score}"); Console.WriteLine(string.Format("Formatted: {0} scored {1}%", name, score)); // StringBuilder StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) { sb.Append("Line " + i + " | "); } Console.WriteLine("StringBuilder result: " + sb.ToString()); // Regex string input = "Email: test@example.com"; Match match = Regex.Match(input, @"\w+@\w+\.\w+"); if (match.Success) Console.WriteLine("Regex found email: " + match.Value); // Parsing and Converting string numStr = "123"; int parsed = int.Parse(numStr); Console.WriteLine("Parsed int: " + parsed); bool success = int.TryParse("abc", out int failed); Console.WriteLine("TryParse success: " + success); // String comparison string a = "Straße"; string b = "Strasse"; bool comparison = String.Compare(a, b, CultureInfo.GetCultureInfo("de-DE"), CompareOptions.IgnoreNonSpace) == 0; Console.WriteLine("German comparison equal: " + comparison); // Working with Span (Advanced) ReadOnlySpanspan = "abcdef".AsSpan().Slice(1, 3); Console.WriteLine("Span slice: " + span.ToString()); // "bcd" // Localization and resources // This would typically use ResourceManager to fetch strings based on language Console.WriteLine("Localized greeting (mock): Bonjour!"); } } }
A delegate is a type-safe function pointer that can hold references to methods with a specific signature. It allows methods to be passed as parameters and executed dynamically.
You declare a delegate type, then create an instance pointing to a method, and finally invoke it like a function.
A multicast delegate can point to and invoke multiple methods using +=
. All methods execute in order.
Events in C# use delegates under the hood and follow the observer pattern. They notify subscribers when an action occurs.
The event
keyword declares an event. You can also create custom add/remove accessors to control how event handlers are managed.
Anonymous methods are inline methods without names, defined using the delegate
keyword, and useful for quick event handling.
Lambda expressions use the =>
operator to define inline anonymous functions, making code cleaner and more concise.
Func
represents a method that returns a value. Action
is for methods that return void.
Predicate
is a delegate that returns a boolean.
Lambdas can "capture" variables from their outer scope, allowing them to access local variables even after the method returns.
Event-driven programming responds to events like button clicks or timers. Delegates and events are foundational to building responsive, interactive apps.
// Demonstrating delegates, events, and lambdas in C# using System; namespace DelegateEventDemo { // Declare a delegate type public delegate void Notify(); class Publisher { // Declare an event based on the delegate public event Notify OnPublish; public void Publish() { Console.WriteLine("Publishing message..."); OnPublish?.Invoke(); // Invoke event if there are subscribers } } class Program { // Method that matches delegate signature static void ShowAlert() { Console.WriteLine("Alert: Message was published!"); } static void Main() { // --- Delegates --- Notify del = ShowAlert; // Create delegate instance del(); // Invoke delegate // --- Multicast delegate --- del += () => Console.WriteLine("Logging: Published event fired."); del(); // Both methods will be called // --- Events --- Publisher publisher = new Publisher(); publisher.OnPublish += ShowAlert; // Subscribe using named method publisher.OnPublish += delegate { // Anonymous method Console.WriteLine("Anonymous subscriber notified."); }; publisher.OnPublish += () => Console.WriteLine("Lambda: Message event received."); // Lambda expression publisher.Publish(); // Trigger event // --- Func, Action, Predicate --- Func<int, int, int> add = (a, b) => a + b; Action<string> greet = name => Console.WriteLine("Hello " + name); Predicate<int> isEven = n => n % 2 == 0; Console.WriteLine("Add: 3 + 4 = " + add(3, 4)); greet("Majid"); Console.WriteLine("Is 6 even? " + isEven(6)); // --- Capturing Variables in Lambdas --- int count = 0; Action increment = () => { count++; Console.WriteLine("Count is now: " + count); }; increment(); // 1 increment(); // 2 // --- Custom event accessor (advanced) --- CustomButton btn = new CustomButton(); btn.OnClick += () => Console.WriteLine("Button clicked!"); btn.Click(); } } // Advanced: Custom accessors for event class CustomButton { private Notify handlers; public event Notify OnClick { add { Console.WriteLine("Handler added."); handlers += value; } remove { Console.WriteLine("Handler removed."); handlers -= value; } } public void Click() { Console.WriteLine("Click detected."); handlers?.Invoke(); } } }
LINQ (Language Integrated Query) is a powerful feature in C# that provides a unified syntax for querying data from different sources like collections, databases, XML, and more. It allows writing queries directly in C# using SQL-like or method-based syntax.
LINQ to Objects refers to using LINQ with in-memory collections like arrays, lists, and dictionaries. It enables filtering, transforming, and aggregating data using concise and readable syntax.
Where()
filters elements based on a condition.
Select()
projects or transforms each element into a new form (e.g., selecting specific fields or transforming data).
OrderBy()
and OrderByDescending()
sort data, while GroupBy()
categorizes elements into groups
based on a key.
Join()
performs inner joins between two collections based on matching keys.
GroupJoin()
performs left joins.
Any()
checks if any elements satisfy a condition.
All()
checks if all elements satisfy a condition.
LINQ provides built-in aggregation methods like Sum()
, Count()
, Min()
, Max()
, and Average()
to compute values across a sequence.
Queries in LINQ are lazily evaluated (deferred execution), meaning they are not executed until you iterate over them (e.g., using foreach).
Methods like ToList()
or Count()
trigger immediate execution.
LINQ to XML allows querying and modifying XML documents using LINQ syntax. LINQ to SQL maps database tables to C# classes, allowing type-safe queries directly on databases.
Custom LINQ operators can be implemented using IEnumerable
and yield return
, enabling you to define reusable query behaviors
for specialized needs.
// Demonstrating LINQ features in C# using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; namespace LINQDemo { class Program { static void Main() { List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 }; // Filtering (Where) var evenNumbers = numbers.Where(n => n % 2 == 0); Console.WriteLine("Even Numbers:"); foreach (var n in evenNumbers) Console.Write(n + " "); Console.WriteLine(); // Projection (Select) var squares = numbers.Select(n => n * n); Console.WriteLine("Squares:"); foreach (var n in squares) Console.Write(n + " "); Console.WriteLine(); // Ordering var ordered = numbers.OrderByDescending(n => n); Console.WriteLine("Descending:"); foreach (var n in ordered) Console.Write(n + " "); Console.WriteLine(); // Grouping var grouped = numbers.GroupBy(n => n % 2 == 0 ? "Even" : "Odd"); foreach (var group in grouped) { Console.WriteLine(group.Key + " Numbers:"); foreach (var value in group) Console.Write(value + " "); Console.WriteLine(); } // Joining var students = new[] { new { ID = 1, Name = "Alice" }, new { ID = 2, Name = "Bob" } }; var scores = new[] { new { StudentID = 1, Score = 90 }, new { StudentID = 2, Score = 80 } }; var joined = students.Join(scores, s => s.ID, sc => sc.StudentID, (s, sc) => new { s.Name, sc.Score }); Console.WriteLine("Student Scores:"); foreach (var item in joined) Console.WriteLine($"{item.Name}: {item.Score}"); // Quantifiers Console.WriteLine("Any number > 5? " + numbers.Any(n => n > 5)); Console.WriteLine("All even? " + numbers.All(n => n % 2 == 0)); // Aggregations Console.WriteLine("Sum: " + numbers.Sum()); Console.WriteLine("Average: " + numbers.Average()); // Deferred vs Immediate Execution var deferred = numbers.Where(n => { Console.WriteLine("Evaluating: " + n); return n > 2; }); Console.WriteLine("Accessing deferred query:"); foreach (var n in deferred) Console.WriteLine("Result: " + n); var immediate = numbers.Where(n => n > 2).ToList(); // Immediate execution // LINQ to XML XElement xml = new XElement("People", new XElement("Person", new XAttribute("name", "Alice")), new XElement("Person", new XAttribute("name", "Bob")) ); var names = xml.Elements("Person").Select(p => p.Attribute("name").Value); Console.WriteLine("Names from XML:"); foreach (var name in names) Console.WriteLine(name); // Custom LINQ Operator var doubled = numbers.DoubleEach(); Console.WriteLine("Custom LINQ (Double):"); foreach (var d in doubled) Console.Write(d + " "); Console.WriteLine(); } } // Extension method: custom LINQ operator public static class CustomLinqExtensions { public static IEnumerable<int> DoubleEach(this IEnumerable<int> source) { foreach (var item in source) { yield return item * 2; } } } }
The System.IO.File
and Directory
classes provide static methods to perform operations like creating, copying, moving,
and deleting files or directories.
You can use File.ReadAllText()
and File.WriteAllText()
for quick operations,
or StreamReader
and StreamWriter
for more control over how text files are accessed line by line.
Use BinaryReader
and BinaryWriter
to read/write binary data (e.g., images, executables, serialized objects).
Stream
is the base class for reading/writing bytes.
StreamReader
and StreamWriter
provide higher-level access to read/write text using streams.
FileSystemWatcher
monitors a folder and raises events when files are created, changed, deleted, or renamed.
It’s useful for building reactive systems.
System.Text.Json
and Newtonsoft.Json
allow converting objects to JSON (serialization) and back (deserialization).
This is commonly used in APIs and configuration files.
XmlSerializer
converts objects to XML format and reads XML back into objects.
It’s useful for config files and interoperable data.
CSVs are plain text files with comma-separated values.
They are easy to read/write using string.Split()
or libraries like CsvHelper
.
Use ReadAllTextAsync()
, WriteAllTextAsync()
, and Stream
with async to perform non-blocking file operations.
Always close or dispose streams using using
blocks.
Handle exceptions, use async for scalability, and avoid hardcoded paths by using Path.Combine()
.
// Demonstrating file I/O and serialization in C# using System; using System.IO; using System.Text.Json; using System.Xml.Serialization; using System.Threading.Tasks; namespace FileIODemo { public class Person { public string Name { get; set; } public int Age { get; set; } } class Program { static async Task Main() { string textPath = "sample.txt"; string jsonPath = "person.json"; string xmlPath = "person.xml"; string csvPath = "data.csv"; // Write to text file File.WriteAllText(textPath, "Hello, File I/O!"); Console.WriteLine("Text written."); // Read from text file string readText = File.ReadAllText(textPath); Console.WriteLine("Read Text: " + readText); // Using StreamWriter and StreamReader using (StreamWriter writer = new StreamWriter("stream.txt")) { writer.WriteLine("Line 1"); writer.WriteLine("Line 2"); } using (StreamReader reader = new StreamReader("stream.txt")) { Console.WriteLine("StreamReader Output:"); while (!reader.EndOfStream) { Console.WriteLine(reader.ReadLine()); } } // JSON Serialization Person person = new Person { Name = "Alice", Age = 30 }; string json = JsonSerializer.Serialize(person); await File.WriteAllTextAsync(jsonPath, json); Console.WriteLine("JSON written."); // JSON Deserialization string readJson = await File.ReadAllTextAsync(jsonPath); Person loaded = JsonSerializer.Deserialize<Person>(readJson); Console.WriteLine($"Loaded from JSON: {loaded.Name}, {loaded.Age}"); // XML Serialization XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person)); using (FileStream fs = new FileStream(xmlPath, FileMode.Create)) { xmlSerializer.Serialize(fs, person); } // XML Deserialization using (FileStream fs = new FileStream(xmlPath, FileMode.Open)) { Person xmlPerson = (Person)xmlSerializer.Deserialize(fs); Console.WriteLine($"Loaded from XML: {xmlPerson.Name}, {xmlPerson.Age}"); } // CSV Write File.WriteAllText(csvPath, "Name,Age\nAlice,30\nBob,25"); // CSV Read Console.WriteLine("CSV Content:"); foreach (string line in File.ReadAllLines(csvPath)) { Console.WriteLine(line); } // FileSystemWatcher (short example) FileSystemWatcher watcher = new FileSystemWatcher("."); watcher.Filter = "*.txt"; watcher.Changed += (s, e) => Console.WriteLine($"File changed: {e.Name}"); watcher.EnableRaisingEvents = true; Console.WriteLine("Watching for changes..."); // Async File I/O await File.WriteAllTextAsync("async.txt", "Async operation completed."); string asyncRead = await File.ReadAllTextAsync("async.txt"); Console.WriteLine("Async read: " + asyncRead); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } } }
In synchronous programming, tasks run one after another, blocking the main thread. Asynchronous programming allows tasks to run in the background without blocking, improving responsiveness and performance—especially for I/O-bound operations.
The Task
and Task<T>
classes represent asynchronous operations. They allow you to run code in parallel threads and return results upon completion.
The async
keyword marks a method as asynchronous, and await
pauses execution until the awaited task completes. This helps write non-blocking, sequential-looking code.
You can start tasks using Task.Run()
. To cancel them safely, use a CancellationTokenSource
and pass its token to the task logic.
You can chain tasks using .ContinueWith()
or use await
in sequence to create dependent workflows.
The Parallel
class (e.g., Parallel.For()
) allows running multiple tasks simultaneously, utilizing multi-core processors effectively.
When multiple threads access shared resources, thread safety must be enforced to avoid race conditions and data corruption.
The lock
keyword ensures that only one thread can access a critical section at a time. Other options include Mutex
, Semaphore
, and Monitor
.
Deadlocks occur when two or more threads wait for each other to release resources. Avoid them by locking resources in a consistent order and minimizing lock scope.
- Avoid async void except in event handlers.
- Use ConfigureAwait(false)
in library code.
- Prefer async/await
over ContinueWith()
.
- Handle exceptions with try/catch
around awaited code.
// Demonstrating async, tasks, cancellation, and synchronization in C# using System; using System.Threading; using System.Threading.Tasks; namespace AsyncDemo { class Program { static async Task Main() { Console.WriteLine("Start Main Thread"); // 1. Basic async/await await DoWorkAsync(); // 2. Using Task<T> int result = await ComputeSumAsync(5, 10); Console.WriteLine("Sum = " + result); // 3. CancellationToken example var cts = new CancellationTokenSource(); var token = cts.Token; var task = CancellableOperationAsync(token); cts.CancelAfter(2000); // cancel after 2 seconds try { await task; } catch (OperationCanceledException) { Console.WriteLine("Task was cancelled."); } // 4. Task continuation Task.Run(() => Console.WriteLine("First Task")) .ContinueWith(t => Console.WriteLine("Then Second")); // 5. Parallel.For example Parallel.For(0, 5, i => { Console.WriteLine($"Parallel loop index {i}"); }); // 6. Thread safety using lock SafeCounter counter = new SafeCounter(); Parallel.For(0, 1000, _ => counter.Increment()); Console.WriteLine("Final Counter Value: " + counter.Value); Console.WriteLine("End Main Thread"); } static async Task DoWorkAsync() { Console.WriteLine("Doing work asynchronously..."); await Task.Delay(1000); // simulate async delay Console.WriteLine("Done!"); } static async Task<int> ComputeSumAsync(int a, int b) { await Task.Delay(500); // simulate work return a + b; } static async Task CancellableOperationAsync(CancellationToken token) { Console.WriteLine("Started cancellable operation..."); for (int i = 0; i < 5; i++) { token.ThrowIfCancellationRequested(); await Task.Delay(1000); // simulate delay Console.WriteLine("Working..."); } } } // Thread-safe counter using lock class SafeCounter { private int _count; private readonly object _lock = new object(); public void Increment() { lock (_lock) { _count++; } } public int Value { get { lock (_lock) { return _count; } } } } }
ADO.NET is a set of classes in the .NET framework for accessing and manipulating data in relational databases. It provides disconnected and connected data access models.
Connections are established using SqlConnection
with a connection string specifying the server, database, and credentials.
Use SqlCommand
to execute SQL statements or stored procedures, including SELECT, INSERT, UPDATE, and DELETE commands.
SqlDataReader
provides fast, forward-only, read-only access to query results. DataSet
represents an in-memory cache of data that can be worked with disconnected from the database.
Parameterized queries use placeholders for parameters in SQL commands, protecting against SQL injection by separating code from data.
EF Core is an object-relational mapper (ORM) that enables developers to work with databases using .NET objects, simplifying data access and reducing boilerplate code.
Code-First starts with C# classes to generate the database schema. Database-First starts from an existing database to generate the data model classes.
LINQ to Entities allows querying the database using LINQ syntax on entity objects, providing compile-time checking and IntelliSense.
Migrations help evolve the database schema as the model changes, enabling version control and incremental updates.
Transactions group multiple database operations into a single unit of work, ensuring all succeed or fail together, preserving data integrity.
// Demonstrating ADO.NET and EF Core basics in C# using System; using System.Data; using System.Data.SqlClient; using System.Linq; using Microsoft.EntityFrameworkCore; namespace DatabaseDemo { // EF Core model class public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } // EF Core DbContext public class AppDbContext : DbContext { public DbSetProducts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) { // Use your connection string here options.UseSqlServer("Server=.;Database=SampleDB;Trusted_Connection=True;"); } } class Program { static void Main() { string connectionString = "Server=.;Database=SampleDB;Trusted_Connection=True;"; // ADO.NET example: Select data using SqlDataReader using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); string sql = "SELECT Id, Name, Price FROM Products WHERE Price > @price"; using (SqlCommand cmd = new SqlCommand(sql, conn)) { cmd.Parameters.AddWithValue("@price", 50); using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { Console.WriteLine($"Product: {reader["Name"]}, Price: {reader["Price"]}"); } } } } // EF Core example: Query with LINQ to Entities using (var context = new AppDbContext()) { var expensiveProducts = context.Products .Where(p => p.Price > 50) .OrderBy(p => p.Name) .ToList(); foreach (var product in expensiveProducts) { Console.WriteLine($"[EF Core] Product: {product.Name}, Price: {product.Price}"); } } // EF Core example: Add and save new product using (var context = new AppDbContext()) { var newProduct = new Product { Name = "New Gadget", Price = 99.99m }; context.Products.Add(newProduct); context.SaveChanges(); Console.WriteLine("Added new product via EF Core."); } // Transaction example with ADO.NET using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); SqlTransaction transaction = conn.BeginTransaction(); try { using (SqlCommand cmd = new SqlCommand("UPDATE Products SET Price = Price * 1.1", conn, transaction)) { int rows = cmd.ExecuteNonQuery(); Console.WriteLine($"Updated prices for {rows} products."); } transaction.Commit(); } catch (Exception ex) { transaction.Rollback(); Console.WriteLine("Transaction failed: " + ex.Message); } } } } }
Windows Forms (WinForms) is a UI framework in .NET for building rich desktop applications. It provides a set of controls and tools to create interactive graphical user interfaces easily.
A WinForms app starts with a Form
class representing a window.
You add controls like buttons and textboxes to the form visually or by code.
Controls are UI elements the user interacts with. Button
triggers actions, TextBox
collects input, and Label
displays text.
Events notify the app when users interact, e.g., clicking a button. Event handlers are methods wired to these events to define responses.
Layout controls such as FlowLayoutPanel
and TableLayoutPanel
help arrange child controls dynamically and responsively.
Use dialogs like OpenFileDialog
or SaveFileDialog
to interact with the file system, and MessageBox
to show alerts and confirmations.
Menus (MenuStrip
) provide navigation and commands; toolbars (ToolStrip
) offer quick access buttons with icons.
Data binding connects UI controls to data sources like databases or collections, automating data display and updates.
The Graphics
class allows drawing shapes, text, and images on forms and controls for custom UI and visualization.
After building your app, you package it with dependencies and distribute it via installers or self-contained executables.
// Simple Windows Forms Application with Button, TextBox, Label and Event Handling using System; using System.Drawing; using System.Windows.Forms; namespace WinFormsDemo { public class MainForm : Form { private Button btnClickMe; private TextBox txtInput; private Label lblMessage; public MainForm() { // Set form properties this.Text = "Windows Forms Demo"; this.Size = new Size(400, 250); this.StartPosition = FormStartPosition.CenterScreen; // Initialize Button btnClickMe = new Button(); btnClickMe.Text = "Click Me"; btnClickMe.Location = new Point(150, 50); btnClickMe.Size = new Size(100, 30); // Wire up click event handler btnClickMe.Click += BtnClickMe_Click; // Initialize TextBox txtInput = new TextBox(); txtInput.Location = new Point(100, 100); txtInput.Size = new Size(200, 25); // Initialize Label lblMessage = new Label(); lblMessage.Location = new Point(100, 140); lblMessage.Size = new Size(200, 30); lblMessage.Text = "Enter text and click the button"; // Add controls to form this.Controls.Add(btnClickMe); this.Controls.Add(txtInput); this.Controls.Add(lblMessage); } // Event handler for Button Click private void BtnClickMe_Click(object sender, EventArgs e) { // Display the input text in the label lblMessage.Text = "Hello, " + txtInput.Text + "!"; } [STAThread] static void Main() { // Enable visual styles for modern look Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // Run the main form Application.Run(new MainForm()); } } }
ASP.NET Core is a modern, open-source, cross-platform framework for building web applications and APIs. It is lightweight, modular, and designed for high performance. ASP.NET Core supports dependency injection, middleware pipeline, and flexible hosting models.
To create a new ASP.NET Core project, you can use the .NET CLI or Visual Studio templates. The project includes configuration files and default folders like Controllers, Views, and wwwroot for static files. It supports SDK-style projects and can be easily deployed cross-platform.
Middleware components form a pipeline that processes HTTP requests and responses. Each middleware can handle requests, perform logic, and pass control to the next middleware. Common middleware includes routing, authentication, and static file serving.
Routing maps incoming requests to specific endpoints such as controllers or Razor Pages. ASP.NET Core supports conventional routing and attribute routing. Endpoints define how HTTP verbs and URL patterns correspond to actions.
Controllers are classes responsible for handling incoming HTTP requests. Actions are methods inside controllers that process requests and return responses. They can return views, JSON, or status codes.
Views are templates used to generate HTML dynamically. Razor is the view engine syntax combining C# code with HTML using special directives. It supports layout pages, partial views, and view components for reusable UI.
Model binding automatically maps data from HTTP requests (form fields, query strings, JSON) to method parameters or model properties. Validation attributes can be applied on models to enforce rules. Validation errors are accessible and can be displayed in views.
ASP.NET Core has built-in dependency injection to manage service lifetimes and dependencies. Services are registered in the startup configuration and injected into constructors. This promotes loose coupling and easier testing.
Configuration settings are loaded from JSON files, environment variables, and other providers. This enables flexible configuration per environment (development, staging, production). The IConfiguration interface allows accessing configuration values.
ASP.NET Core provides a logging API that supports multiple providers like Console, Debug, and third-party frameworks. Logs can be filtered by severity levels such as Information, Warning, and Error. Structured logging enables capturing detailed contextual data for diagnostics.
// A minimal ASP.NET Core Web API example with controller, routing, DI, and logging using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.Mvc; var builder = WebApplication.CreateBuilder(args); // Register services for dependency injection builder.Services.AddControllers(); var app = builder.Build(); // Middleware pipeline setup if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); // detailed error page in development } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); // map controller endpoints }); app.Run(); // Example Controller [ApiController] [Route("api/[controller]")] public class HelloController : ControllerBase { private readonly ILogger<HelloController> _logger; // Constructor injection of ILogger public HelloController(ILogger<HelloController> logger) { _logger = logger; } // GET api/hello [HttpGet] public IActionResult Get() { _logger.LogInformation("GET request received at /api/hello"); return Ok(new { Message = "Hello, ASP.NET Core!" }); } }
Authentication verifies user identity, often via cookies, tokens, or OAuth. Authorization controls what authenticated users can access. ASP.NET Core supports schemes like JWT Bearer, cookie authentication, and policies for role or claim-based access control.
EF Core integrates smoothly with ASP.NET Core for database operations. It supports LINQ queries, migrations, change tracking, and async data access to build efficient and maintainable data layers.
ASP.NET Core enables building RESTful APIs using controllers or minimal APIs. It supports routing, model binding, validation, content negotiation, and returns JSON by default.
Swagger (via Swashbuckle) automatically generates interactive API documentation from your ASP.NET Core APIs. It helps developers understand and test endpoints directly through a web UI.
Custom middleware components allow inserting your own logic into the request pipeline. Middleware can modify requests/responses, handle errors, and add headers.
Caching improves performance by storing frequently requested data. ASP.NET Core supports in-memory caching, distributed caches (Redis), and response caching with flexible expiration policies.
SignalR is a library that enables server-client real-time communication. It supports WebSockets and fallback transports to push live updates to browsers or other clients.
Centralized error handling middleware captures unhandled exceptions. It enables consistent logging, user-friendly error responses, and improved app stability.
ASP.NET Core provides built-in health check middleware to monitor app components and dependencies. It can report status to load balancers or monitoring systems for proactive maintenance.
ASP.NET Core apps can be deployed to Azure App Services or IIS. Deployment involves publishing the app, configuring environment variables, and setting up web server reverse proxies.
// ASP.NET Core setup showing Swagger and custom middleware using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); // Register services including controllers and Swagger generator builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); }); var app = builder.Build(); // Enable middleware for Swagger UI in development if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); }); } // Custom middleware to log request path app.Use(async (context, next) => { Console.WriteLine("Handling request: " + context.Request.Path); await next.Invoke(); // call the next middleware Console.WriteLine("Finished handling request."); }); app.UseRouting(); app.UseAuthorization(); app.MapControllers(); app.Run();
Design patterns are proven solutions to common software design problems. They provide templates for organizing code to improve readability, maintainability, and flexibility. Using patterns helps developers avoid reinventing the wheel and build scalable software.
Ensures a class has only one instance and provides a global access point. Commonly used for logging, configuration, or managing shared resources.
Creates objects without exposing the instantiation logic to the client. Allows selecting the type of object to create at runtime, improving flexibility.
Defines a one-to-many dependency so that when one object changes state, its dependents are notified and updated automatically. Useful for event-driven systems and UI updates.
Adds behavior to objects dynamically without altering their class. Enables flexible and reusable code by wrapping objects with additional functionality.
Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Lets the algorithm vary independently from clients that use it.
Provides a layer to abstract data access logic. Helps decouple business logic from data storage details and supports unit testing.
Injects dependencies into a class rather than having the class create them. Promotes loose coupling, easier testing, and better modularity.
Separates application logic into Model (data), View (UI), and Controller (input logic). Improves organization and supports parallel development and testing.
Applying patterns appropriately leads to maintainable and scalable applications. Combining multiple patterns is common in enterprise software development.
// Example of Singleton and Factory Patterns in C# using System; // Singleton Pattern: Logger class with a single instance public sealed class Logger { private static readonly Logger _instance = new Logger(); // Private constructor prevents external instantiation private Logger() { } public static Logger Instance { get { return _instance; } } public void Log(string message) { Console.WriteLine("[LOG]: " + message); } } // Factory Pattern: Creates different types of notifications public interface INotification { void Notify(string message); } public class EmailNotification : INotification { public void Notify(string message) { Console.WriteLine("Email sent: " + message); } } public class SmsNotification : INotification { public void Notify(string message) { Console.WriteLine("SMS sent: " + message); } } public static class NotificationFactory { public static INotification CreateNotification(string type) { switch(type.ToLower()) { case "email": return new EmailNotification(); case "sms": return new SmsNotification(); default: throw new ArgumentException("Unknown notification type"); } } } class Program { static void Main() { // Using Singleton Logger Logger.Instance.Log("Application started"); // Using Factory to create notifications INotification notification = NotificationFactory.CreateNotification("email"); notification.Notify("Hello via Email"); notification = NotificationFactory.CreateNotification("sms"); notification.Notify("Hello via SMS"); Logger.Instance.Log("Application ended"); } }
Unit testing involves testing individual parts of code, typically methods or classes, to verify they work correctly in isolation. It helps catch bugs early and ensures code quality throughout development.
MSTest is Microsoft’s built-in testing framework for .NET.
It provides attributes like [TestClass]
and [TestMethod]
to designate test classes and methods.
NUnit and xUnit are popular alternative testing frameworks with rich features and community support. They offer advanced assertions, test discovery, and integration with various tools.
Test cases verify specific functionality by asserting expected outcomes against actual results.
Assertions like Assert.AreEqual
or Assert.ThrowsException
confirm code behaves as intended.
Mocking frameworks like Moq allow creating fake objects for dependencies to isolate the unit under test. This is useful when testing code that interacts with databases, APIs, or external services.
Integration tests verify that multiple components or systems work together correctly. They typically involve databases, web services, or other external dependencies.
TDD is a development approach where tests are written before the production code. It drives design and ensures code is always tested, improving reliability and maintainability.
Code coverage tools measure how much of your code is exercised by tests, identifying untested parts. Common tools include Visual Studio Coverage, Coverlet, and dotCover.
CI systems automatically run tests on each code check-in to detect issues early. Popular CI tools include GitHub Actions, Azure DevOps, and Jenkins.
- Write small, focused tests.
- Name tests clearly to describe behavior.
- Avoid test dependencies and side effects.
- Keep tests fast and reliable.
- Use mocking wisely to isolate tests.
// Example MSTest unit test with Moq mocking a dependency using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using System; namespace TestingDemo { // Interface to be mocked public interface ICalculatorService { int Add(int a, int b); } // Class under test public class MathClient { private readonly ICalculatorService _calculator; public MathClient(ICalculatorService calculator) { _calculator = calculator; } public int AddNumbers(int x, int y) { return _calculator.Add(x, y); } } [TestClass] public class MathClientTests { private Mock_mockCalculator; private MathClient _mathClient; [TestInitialize] public void Setup() { // Create mock for ICalculatorService _mockCalculator = new Mock (); // Inject mock into MathClient _mathClient = new MathClient(_mockCalculator.Object); } [TestMethod] public void AddNumbers_ReturnsCorrectSum() { // Arrange: setup mock to return sum of parameters _mockCalculator.Setup(calc => calc.Add(It.IsAny (), It.IsAny ())) .Returns((int a, int b) => a + b); // Act int result = _mathClient.AddNumbers(3, 5); // Assert Assert.AreEqual(8, result, "AddNumbers should return the sum of two numbers"); // Verify that Add method was called once with 3 and 5 _mockCalculator.Verify(calc => calc.Add(3, 5), Times.Once); } } }
HttpClient
is a powerful class to send HTTP requests and receive responses from RESTful APIs.
It supports asynchronous calls, headers, and JSON/XML serialization.
Using ASP.NET Core Web API, you can create RESTful services with controllers that expose CRUD endpoints. The framework supports routing, model binding, and content negotiation.
SOAP is a protocol for exchanging structured information in web services. Though less popular than REST, it is still used in legacy systems and supports strict contracts via WSDL.
APIs often use JSON or XML to exchange data. .NET provides libraries like System.Text.Json and XmlSerializer for serializing and deserializing these formats.
Secure APIs require authentication. OAuth provides delegated access, while JWT (JSON Web Tokens) are widely used for stateless token-based authentication.
Versioning ensures backward compatibility as APIs evolve. Strategies include URL versioning, query parameters, headers, or media type versioning.
To prevent abuse and overload, APIs implement rate limiting. Clients should handle HTTP 429 responses and retry after a specified time.
Swagger (OpenAPI) generates interactive API documentation. It helps developers understand endpoints, request/response formats, and test APIs directly.
gRPC is a high-performance RPC framework using HTTP/2. It supports strongly typed contracts via Protocol Buffers and is suitable for microservices and real-time communication.
Proper error handling involves checking HTTP status codes, catching exceptions, and providing meaningful messages to clients. Use try-catch blocks and validate responses when calling APIs.
// Example: Calling a REST API asynchronously with HttpClient and handling JSON using System; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using System.Text.Json; namespace ApiClientDemo { // Define a model matching the JSON structure public class Post { public int UserId { get; set; } public int Id { get; set; } public string Title { get; set; } public string Body { get; set; } } class Program { static async Task Main() { using HttpClient client = new HttpClient(); // Base URL for JSONPlaceholder API (free fake REST API) client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/"); client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); try { // Send GET request to /posts/1 HttpResponseMessage response = await client.GetAsync("posts/1"); response.EnsureSuccessStatusCode(); // Throw if not successful // Read JSON response as string string jsonData = await response.Content.ReadAsStringAsync(); // Deserialize JSON to Post object Post post = JsonSerializer.Deserialize(jsonData, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); Console.WriteLine($"Post ID: {post.Id}"); Console.WriteLine($"Title: {post.Title}"); Console.WriteLine($"Body: {post.Body}"); } catch (HttpRequestException e) { Console.WriteLine("Request error: " + e.Message); } catch (Exception ex) { Console.WriteLine("Unexpected error: " + ex.Message); } } } }
Reflection is a powerful feature in C# that allows a program to inspect and manipulate its own metadata at runtime. It enables accessing information about assemblies, types, properties, methods, and attributes dynamically. Attributes are metadata annotations you can apply to classes, methods, or properties, which can be retrieved at runtime using reflection to control behavior or enforce rules.
The dynamic
type in C# bypasses compile-time type checking, enabling flexible, late-bound operations resolved at runtime.
This is useful when interacting with COM objects, dynamic languages, or JSON data.
ExpandoObject
allows you to create objects whose members can be added or removed at runtime, enabling flexible and dynamic data structures.
The .NET runtime handles memory management through garbage collection (GC), which automatically frees memory occupied by objects no longer in use. Understanding how GC works, including generations and the large object heap, helps optimize app performance and avoid memory leaks or excessive allocations.
Unsafe code allows direct manipulation of memory using pointers, similar to languages like C or C++. It requires explicit permission and is used in performance-critical scenarios where low-level memory access is necessary. Writing unsafe code demands careful handling to avoid security risks and program crashes.
Profiling tools help identify bottlenecks in CPU usage, memory, or I/O operations. Optimization techniques include improving algorithms, minimizing allocations, and using efficient data structures. Regular profiling ensures your app runs efficiently without unnecessary resource consumption.
Span<T>
and Memory<T>
are types that provide safe and efficient ways to handle slices of contiguous memory without copying data.
They enable high-performance scenarios such as parsing or I/O buffering by reducing allocations and improving cache usage.
Source generators use the Roslyn compiler platform to analyze code and generate additional source files during compilation. This technique automates repetitive coding tasks, reduces boilerplate, and can improve maintainability by generating strongly typed code at compile time.
Multithreading enables concurrent execution of multiple code paths, improving responsiveness and throughput. Effective multithreading requires understanding synchronization primitives like locks and avoiding race conditions and deadlocks to maintain thread safety.
Secure coding practices include validating all inputs, using parameterized queries to prevent SQL injection, encrypting sensitive data, and following the principle of least privilege. Using secure libraries and frameworks, regularly updating dependencies, and handling exceptions properly are essential to reduce vulnerabilities.
Certifications such as Microsoft Certified: C# Programmer validate your expertise and enhance your career prospects. Preparation involves mastering core language features, understanding .NET fundamentals, practicing coding exercises, and studying exam objectives thoroughly.
// Demonstration of Reflection and ExpandoObject usage in C# using System; using System.Dynamic; using System.Reflection; // Custom attribute definition [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class InfoAttribute : Attribute { public string Description { get; } public InfoAttribute(string description) { Description = description; } } // Sample class decorated with custom attribute [Info("This is a sample class demonstrating reflection")] public class SampleClass { public string Name { get; set; } = "DefaultName"; public void PrintName() { Console.WriteLine("Name is: " + Name); } } class Program { static void Main() { // Using Reflection to get custom attributes Type type = typeof(SampleClass); object[] attrs = type.GetCustomAttributes(typeof(InfoAttribute), false); if (attrs.Length > 0) { var info = (InfoAttribute)attrs[0]; Console.WriteLine("Class attribute description: " + info.Description); } // Creating an ExpandoObject dynamically dynamic expando = new ExpandoObject(); expando.FirstName = "John"; expando.LastName = "Doe"; expando.Age = 30; Console.WriteLine($"ExpandoObject: {expando.FirstName} {expando.LastName}, Age {expando.Age}"); // Adding a method dynamically expando.GetFullName = (Func)(() => $"{expando.FirstName} {expando.LastName}"); Console.WriteLine("Full name via ExpandoObject method: " + expando.GetFullName()); } }
Serialization is the process of converting an object into a format (such as JSON or XML) that can be stored or transmitted. Deserialization is the reverse process, reconstructing the object from the serialized data. This is essential for saving data or communicating with web services.
System.Text.Json
is the modern built-in library in .NET Core and .NET 5+ for JSON serialization and deserialization.
It is lightweight, high-performance, and supports most common JSON features.
You can customize serialization behavior using JsonSerializerOptions
, including property naming policies, ignoring null values, and controlling case sensitivity.
Serialization can handle nested objects and collections, converting the entire object graph into JSON. Proper handling ensures data integrity and correct deserialization.
Custom converters allow you to control how specific types are serialized and deserialized. This is useful for complex types or non-standard JSON formats.
Attributes like [JsonIgnore]
let you exclude properties from serialization to protect sensitive data or reduce payload size.
Newtonsoft.Json is a popular third-party library with rich features, widely used before System.Text.Json. It supports more flexible customization, LINQ to JSON, and advanced scenarios.
Collections like lists, arrays, and dictionaries serialize naturally into JSON arrays or objects, preserving their structure.
Performance depends on object complexity, size, and library choice. System.Text.Json is faster for most cases, but Newtonsoft.Json offers more features.
Serialized data can contain sensitive information. Ensure data is encrypted or sanitized as needed before storage or transmission.
// Example demonstrating JSON serialization and deserialization using System.Text.Json using System; using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; // Sample class representing a user public class User { public string Name { get; set; } public int Age { get; set; } // Ignore this property during serialization [JsonIgnore] public string Password { get; set; } public List<string> Roles { get; set; } } class Program { static void Main() { // Create a new user object User user = new User { Name = "Alice", Age = 28, Password = "secret123", Roles = new List<string> { "Admin", "Editor" } }; // Configure JSON serialization options var options = new JsonSerializerOptions { WriteIndented = true, // Pretty-print JSON PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // camelCase properties DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull // Ignore nulls }; // Serialize user object to JSON string string jsonString = JsonSerializer.Serialize(user, options); Console.WriteLine("Serialized JSON:"); Console.WriteLine(jsonString); // Deserialize JSON back to User object User deserializedUser = JsonSerializer.Deserialize<User>(jsonString, options); Console.WriteLine("\nDeserialized User:"); Console.WriteLine($"Name: {deserializedUser.Name}"); Console.WriteLine($"Age: {deserializedUser.Age}"); Console.WriteLine($"Password (ignored): {deserializedUser.Password ?? "null"}"); Console.WriteLine("Roles: " + string.Join(", ", deserializedUser.Roles)); } }
RESTful APIs follow REST architecture principles, using HTTP methods (GET, POST, PUT, DELETE) to perform CRUD operations. They are stateless, use resource URIs, and support standard response codes.
ASP.NET Core makes it easy to build APIs with controllers, routing, model binding, and built-in dependency injection. The framework supports middleware for cross-cutting concerns like logging and authentication.
Routing maps HTTP requests to controller actions. Attribute routing enables decorating controllers and actions with route templates for flexible URL patterns.
Model binding automatically maps request data (query, body, headers) to action parameters. Validation attributes ensure data integrity, returning errors when invalid input is detected.
Controllers return status codes like 200 OK, 201 Created, 400 Bad Request, or 404 Not Found using IActionResult. This helps clients understand the outcome of their requests.
API versioning prevents breaking changes by supporting multiple versions concurrently. Versions can be specified via URL segments, query strings, or headers.
Protect APIs using authentication schemes (JWT, OAuth2) and authorization policies to restrict access based on roles or claims.
HttpClient allows C# apps to send HTTP requests to APIs and process responses asynchronously.
Swagger (OpenAPI) auto-generates interactive documentation, letting developers explore and test API endpoints.
Postman is a popular tool for manual API testing. Automated integration tests verify end-to-end API functionality during development.
// Simple ASP.NET Core Web API example with routing, model binding, and status codes using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; namespace WebApiDemo.Controllers { [ApiController] [Route("api/[controller]")] public class ProductsController : ControllerBase { // In-memory sample data private static readonly List<Product> Products = new List<Product> { new Product { Id = 1, Name = "Laptop", Price = 1200 }, new Product { Id = 2, Name = "Smartphone", Price = 800 } }; // GET api/products [HttpGet] public ActionResult<IEnumerable<Product>> GetAll() { return Ok(Products); // Returns 200 OK with list of products } // GET api/products/1 [HttpGet("{id}")] public ActionResult<Product> GetById(int id) { var product = Products.Find(p => p.Id == id); if (product == null) return NotFound(); // Returns 404 if not found return Ok(product); } // POST api/products [HttpPost] public ActionResult<Product> Create(Product newProduct) { newProduct.Id = Products.Count + 1; Products.Add(newProduct); return CreatedAtAction(nameof(GetById), new { id = newProduct.Id }, newProduct); // Returns 201 Created with location header } } // Simple Product model public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } } }
Entity Framework Core (EF Core) is a modern object-relational mapper (ORM) for .NET that enables developers to work with databases using .NET objects. It abstracts database operations into strongly typed code, improving productivity and maintainability.
The DbContext
class represents a session with the database and allows querying and saving data.
Models are C# classes representing database tables. You configure DbContext
to connect to your database and include DbSet properties for each model.
EF Core supports creating, reading, updating, and deleting (CRUD) operations via LINQ queries and DbSet methods such as Add
, Find
, Update
, and Remove
.
LINQ queries let you filter, sort, and project data from your models. EF Core translates these LINQ expressions into optimized SQL queries.
Migrations manage database schema changes by generating incremental scripts. You create migrations after modifying models and apply them to update the database schema without losing data.
EF Core supports defining relationships between models using navigation properties and data annotations or Fluent API. Proper configuration ensures referential integrity and cascading behaviors.
Data loading strategies control when related data is fetched:
- Eager loading fetches related data with the main query using Include
.
- Lazy loading fetches data on demand.
- Explicit loading manually loads related data after the initial query.
Performance can be improved by optimizing queries, limiting data loaded, using projections, and caching. Profiling tools help identify slow queries.
EF Core supports database transactions to group operations atomically. Concurrency control prevents conflicting data updates using optimistic or pessimistic locking.
When needed, raw SQL queries can be executed using FromSqlRaw
or ExecuteSqlRaw
for complex or performance-critical scenarios.
// EF Core example demonstrating DbContext configuration and CRUD operations using System; using System.Collections.Generic; using System.Linq; using Microsoft.EntityFrameworkCore; // Define the Product model representing a database table public class Product { public int Id { get; set; } // Primary key public string Name { get; set; } public decimal Price { get; set; } } // Define the application's DbContext public class AppDbContext : DbContext { // DbSet represents the Products table public DbSetProducts { get; set; } // Configure the database connection string (using SQLite for demo) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite("Data Source=app.db"); } } class Program { static void Main() { using var context = new AppDbContext(); // Ensure database and tables are created context.Database.EnsureCreated(); // CREATE: Add new product var newProduct = new Product { Name = "Tablet", Price = 299.99m }; context.Products.Add(newProduct); context.SaveChanges(); Console.WriteLine($"Added Product: {newProduct.Name} with Id {newProduct.Id}"); // READ: Query products with LINQ var products = context.Products.Where(p => p.Price > 100).ToList(); Console.WriteLine("Products with price > 100:"); foreach (var product in products) { Console.WriteLine($"- {product.Name}: ${product.Price}"); } // UPDATE: Modify a product var productToUpdate = context.Products.FirstOrDefault(p => p.Name == "Tablet"); if (productToUpdate != null) { productToUpdate.Price = 279.99m; context.SaveChanges(); Console.WriteLine($"Updated Product {productToUpdate.Name} price to ${productToUpdate.Price}"); } // DELETE: Remove a product var productToDelete = context.Products.FirstOrDefault(p => p.Name == "Tablet"); if (productToDelete != null) { context.Products.Remove(productToDelete); context.SaveChanges(); Console.WriteLine($"Deleted Product {productToDelete.Name}"); } } }
Awareness of common security threats like injection attacks, data breaches, and unauthorized access is essential. Understanding attack vectors helps in designing secure applications.
Passwords should never be stored in plain text. Use strong cryptographic hashing algorithms (e.g., bcrypt, PBKDF2) with salts to securely store passwords.
Secure communication by enforcing HTTPS and SSL/TLS protocols to encrypt data transmitted between clients and servers.
Always validate and sanitize user inputs to prevent malicious data from causing harm such as injection or cross-site scripting.
Use parameterized queries or ORM tools to avoid SQL injection vulnerabilities, which allow attackers to manipulate database queries.
XSS attacks inject malicious scripts into web pages, while CSRF tricks users into unwanted actions. Use output encoding and anti-forgery tokens to prevent these.
Restrict access based on user roles or claims, ensuring users can only access permitted resources or operations.
ASP.NET Core Identity provides robust user management, including authentication, password recovery, and multi-factor authentication.
Protect sensitive configuration settings like connection strings or API keys using secure storage options such as Azure Key Vault or environment variables.
Use automated tools for static code analysis, vulnerability scanning, and penetration testing to detect and fix security issues early.
// Example demonstrating password hashing and verification using PBKDF2 in C# using System; using System.Security.Cryptography; using System.Text; public class PasswordHasher { // Hashes a password with a salt using PBKDF2 public static string HashPassword(string password) { // Generate a 16-byte salt using a secure PRNG byte[] salt = new byte[16]; using (var rng = RandomNumberGenerator.Create()) { rng.GetBytes(salt); } // Derive a 32-byte subkey (hash) using PBKDF2 with 100,000 iterations var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000, HashAlgorithmName.SHA256); byte[] hash = pbkdf2.GetBytes(32); // Combine salt and hash byte[] hashBytes = new byte[48]; // 16 salt + 32 hash Array.Copy(salt, 0, hashBytes, 0, 16); Array.Copy(hash, 0, hashBytes, 16, 32); // Convert to Base64 for storage return Convert.ToBase64String(hashBytes); } // Verifies a password against a stored hash public static bool VerifyPassword(string storedHash, string passwordToCheck) { byte[] hashBytes = Convert.FromBase64String(storedHash); // Extract salt (first 16 bytes) byte[] salt = new byte[16]; Array.Copy(hashBytes, 0, salt, 0, 16); // Compute hash of the password to check using the extracted salt var pbkdf2 = new Rfc2898DeriveBytes(passwordToCheck, salt, 100000, HashAlgorithmName.SHA256); byte[] hash = pbkdf2.GetBytes(32); // Compare computed hash with stored hash for (int i = 0; i < 32; i++) { if (hashBytes[i + 16] != hash[i]) return false; } return true; } } class Program { static void Main() { string password = "MySecurePassword!"; // Hash the password string hashedPassword = PasswordHasher.HashPassword(password); Console.WriteLine("Stored Hash: " + hashedPassword); // Verify password - should be true bool isCorrect = PasswordHasher.VerifyPassword(hashedPassword, password); Console.WriteLine("Password verification (correct password): " + isCorrect); // Verify wrong password - should be false bool isIncorrect = PasswordHasher.VerifyPassword(hashedPassword, "WrongPassword"); Console.WriteLine("Password verification (wrong password): " + isIncorrect); } }
WPF is a UI framework for building desktop client applications on Windows. It uses DirectX for rendering and supports rich graphics, controls, and multimedia.
XAML is an XML-based markup language used to define UI elements declaratively. Data binding connects UI elements to data sources, enabling dynamic updates and separation of concerns.
WPF provides a variety of controls (buttons, text boxes, lists) and layout panels (Grid, StackPanel, DockPanel) to arrange UI elements responsively.
MVVM is a design pattern that separates UI (View), business logic (ViewModel), and data (Model), facilitating testability and maintainability.
Commands encapsulate actions triggered by UI elements. Routed events travel up or down the visual tree, enabling flexible event handling.
Styles define reusable property sets, templates control the visual structure of controls, and themes provide consistent look and feel across the app.
WPF supports rich animations and visual state management to create interactive, visually appealing applications.
Resources such as brushes and styles can be reused app-wide. Localization enables apps to support multiple languages and cultures.
Tools like Visual Studio's WPF Inspector help debug UI. Performance tips include virtualization, deferred loading, and minimizing layout passes.
WPF apps can be packaged using ClickOnce or MSIX for easy deployment and updates.
// MainWindow.xaml (UI definition using XAML) <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WPF MVVM Example" Height="200" Width="300"> <Grid Margin="10"> <StackPanel> <TextBox Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" Width="200" Margin="0,0,0,10" /> <Button Content="Click Me" Command="{Binding ClickCommand}" Width="200" /> <TextBlock Text="{Binding Message}" Margin="0,10,0,0" FontWeight="Bold" /> </StackPanel> </Grid> </Window> // MainWindow.xaml.cs (code-behind) using System.Windows; namespace WpfApp { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DataContext = new ViewModel(); // Set DataContext to ViewModel } } } // ViewModel.cs (implements INotifyPropertyChanged and command) using System.ComponentModel; using System.Windows.Input; namespace WpfApp { public class ViewModel : INotifyPropertyChanged { private string userName; private string message; public string UserName { get => userName; set { userName = value; OnPropertyChanged(nameof(UserName)); } } public string Message { get => message; set { message = value; OnPropertyChanged(nameof(Message)); } } public ICommand ClickCommand { get; } public ViewModel() { ClickCommand = new RelayCommand(OnButtonClick); } private void OnButtonClick(object parameter) { Message = $"Hello, {UserName}!"; } public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } // RelayCommand.cs (simplified ICommand implementation) using System; using System.Windows.Input; namespace WpfApp { public class RelayCommand : ICommand { private readonly Action<object> execute; private readonly Func<object, bool> canExecute; public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null) { this.execute = execute; this.canExecute = canExecute; } public bool CanExecute(object parameter) => canExecute == null || canExecute(parameter); public void Execute(object parameter) => execute(parameter); public event EventHandler CanExecuteChanged { add => CommandManager.RequerySuggested += value; remove => CommandManager.RequerySuggested -= value; } } }
Networking in C# involves using namespaces like System.Net
and System.Net.Sockets
to create client-server applications that communicate over TCP or UDP protocols.
TCP provides reliable, connection-oriented communication, while UDP is connectionless and faster but less reliable.
C# supports both with classes such as TcpClient
, TcpListener
, and UdpClient
.
The HttpClient
class allows sending HTTP requests and receiving responses asynchronously, making it suitable for REST API consumption.
WebSockets enable full-duplex communication channels over a single TCP connection, useful for real-time apps.
.NET provides WebSocket support via System.Net.WebSockets
.
REST clients use HttpClient
, while REST services can be built with ASP.NET Core Web API. Both follow REST principles for resource manipulation.
gRPC is a high-performance, open-source RPC framework that uses Protocol Buffers for serialization. .NET supports gRPC to build efficient client-server communication.
Async-await patterns improve responsiveness in network programming by not blocking threads while waiting for network I/O.
Proper error handling includes catching exceptions, retry policies, and managing timeouts to avoid hanging connections.
Secure sockets layer (SSL) and Transport Layer Security (TLS) encrypt data transmitted over the network, protecting against eavesdropping and tampering.
Optimizations include connection pooling, minimizing data sent, compression, and efficient socket management to improve throughput and reduce latency.
// TCP Server example: listens for client connections and echoes received messages using System; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; class TcpServer { private const int Port = 5000; public static async Task StartServerAsync() { TcpListener listener = new TcpListener(IPAddress.Any, Port); listener.Start(); Console.WriteLine($"Server started on port {Port}."); while (true) { TcpClient client = await listener.AcceptTcpClientAsync(); Console.WriteLine("Client connected."); _ = HandleClientAsync(client); // Fire and forget } } private static async Task HandleClientAsync(TcpClient client) { NetworkStream stream = client.GetStream(); byte[] buffer = new byte[1024]; int bytesRead; try { while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) != 0) { string received = Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine($"Received: {received}"); // Echo back the message byte[] response = Encoding.UTF8.GetBytes("Echo: " + received); await stream.WriteAsync(response, 0, response.Length); } } catch (Exception ex) { Console.WriteLine($"Exception: {ex.Message}"); } finally { client.Close(); Console.WriteLine("Client disconnected."); } } } class TcpClientExample { public static async Task RunClientAsync() { using TcpClient client = new TcpClient(); await client.ConnectAsync("127.0.0.1", 5000); Console.WriteLine("Connected to server."); NetworkStream stream = client.GetStream(); string message = "Hello, Server!"; byte[] data = Encoding.UTF8.GetBytes(message); await stream.WriteAsync(data, 0, data.Length); Console.WriteLine($"Sent: {message}"); byte[] buffer = new byte[1024]; int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); string response = Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine($"Received: {response}"); } } class Program { static async Task Main() { // Start server in background _ = TcpServer.StartServerAsync(); // Wait a moment for server to start await Task.Delay(500); // Run client await TcpClientExample.RunClientAsync(); Console.WriteLine("Press Enter to exit."); Console.ReadLine(); } }
Microservices architecture breaks applications into small, independent services that communicate over networks. Each service owns its data and can be developed, deployed, and scaled independently.
ASP.NET Core is ideal for building microservices due to its modularity, lightweight footprint, and built-in support for REST APIs and gRPC.
Service discovery helps locate microservices dynamically at runtime. API gateways act as single entry points that route requests, provide authentication, and aggregate responses.
Microservices communicate via HTTP REST APIs, gRPC for high performance, or asynchronous messaging systems like RabbitMQ or Kafka.
Each microservice manages its own database (database per service). Event sourcing records state changes as a sequence of events for auditability and consistency.
Distributed transactions span multiple microservices and can be handled using the Saga pattern, which coordinates local transactions through compensation actions.
Docker packages microservices into containers for consistent deployment environments across machines and clouds.
Kubernetes automates deployment, scaling, and management of containerized microservices, handling service discovery, load balancing, and fault tolerance.
Centralized logging, metrics collection, and tracing tools like Prometheus and Jaeger help monitor health and diagnose issues.
Security involves authentication, authorization, encryption, and secure communication between services, often using OAuth2 and JWT tokens.
// Minimal ASP.NET Core microservice exposing a simple REST API using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Hosting; var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); // In-memory data store for demo var products = new List<Product> { new Product { Id = 1, Name = "Laptop", Price = 1200 }, new Product { Id = 2, Name = "Phone", Price = 700 } }; // GET /products - returns all products app.MapGet("/products", () => products); // GET /products/{id} - returns product by id app.MapGet("/products/{id:int}", (int id) => { var product = products.FirstOrDefault(p => p.Id == id); return product is not null ? Results.Ok(product) : Results.NotFound(); }); app.Run(); // Product model class public record Product { public int Id { get; init; } public string Name { get; init; } public decimal Price { get; init; } }
Reactive Programming is a paradigm focused on asynchronous data streams and the propagation of change. Rx.NET is a library that enables composing asynchronous and event-based programs using observable sequences.
An Observable produces data or events, while an Observer subscribes to receive those events. Observers implement the IObserver<T>
interface.
Observables can be created from various sources such as events, tasks, or collections. Subscribing starts the data flow to observers.
Operators like Where
, Select
, Take
, and Skip
allow filtering and mapping the data streams.
Streams can be combined using operators like Merge
, Concat
, and Zip
to create complex event flows.
Rx.NET provides mechanisms to handle errors gracefully, such as Catch
and Retry
.
Scheduling controls the execution context of observables, enabling concurrency and thread switching with schedulers.
Subjects act as both observers and observables. Cold observables produce data on subscription, while hot observables produce data regardless of subscriptions.
Rx.NET is useful for UI event handling, real-time data processing, asynchronous workflows, and more.
Testing involves verifying emitted sequences, timings, and side effects using tools like TestScheduler.
// Rx.NET example demonstrating creating an observable, filtering, and subscription using System; using System.Reactive.Linq; class Program { static void Main() { // Create an observable sequence emitting integers from 1 to 10 var numbers = Observable.Range(1, 10); // Filter even numbers and multiply by 10 var processedNumbers = numbers .Where(n => n % 2 == 0) // Keep even numbers .Select(n => n * 10); // Multiply each by 10 // Subscribe to the processed observable and print results processedNumbers.Subscribe( onNext: n => Console.WriteLine($"Received: {n}"), onError: ex => Console.WriteLine($"Error: {ex.Message}"), onCompleted: () => Console.WriteLine("Sequence completed.") ); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } }
C# is widely used for game development, especially with engines like Unity. It allows scripting of game logic, handling user input, and managing game objects and scenes.
Unity is a popular cross-platform game engine that uses C# for scripting game behaviors, physics, and UI. Scripts are attached to GameObjects to control their actions.
The game loop repeatedly updates game state and renders frames. Unity provides methods like Update()
and FixedUpdate()
for frame and physics updates.
Unity's Input class captures keyboard, mouse, touch, and controller input to interact with games.
Unity supports both 2D and 3D graphics, providing tools for sprites, meshes, materials, and shaders.
Unity includes a physics engine with rigidbodies and colliders to simulate real-world physics and detect collisions.
Sound effects and music can be played via AudioSources, while animations are managed with the Animator component and animation clips.
Scenes are individual game levels or screens. Unity's UI system allows building menus, HUDs, and interactive elements.
Unity provides debugging tools and profilers to optimize game performance and fix bugs.
Games can be published to multiple platforms including PC, consoles, mobile, and web using Unity’s build system.
// Unity C# script to move a player using keyboard input using UnityEngine; public class PlayerMovement : MonoBehaviour { // Speed of the player public float speed = 5f; // Update is called once per frame void Update() { // Get horizontal and vertical input (arrow keys, WASD) float moveX = Input.GetAxis("Horizontal"); // Left/Right float moveZ = Input.GetAxis("Vertical"); // Forward/Back // Create movement vector Vector3 movement = new Vector3(moveX, 0, moveZ) * speed * Time.deltaTime; // Move the player by updating the position transform.Translate(movement, Space.World); } }
Machine Learning (ML) allows computers to learn patterns from data and make predictions or decisions without explicit programming.
ML.NET is a powerful open-source ML library for .NET developers, providing tools to build, train, and deploy models using C#.
Data preparation involves cleaning and transforming raw data into a format suitable for machine learning models.
Feature engineering is creating new input variables from raw data to improve model accuracy and learning.
Techniques like min-max scaling and z-score normalization adjust features to a common scale for better training performance.
Methods such as oversampling and undersampling balance datasets where one class dominates, improving model fairness.
Includes classification and regression techniques where models learn from labeled data to predict outcomes.
Clustering algorithms find patterns in unlabeled data by grouping similar data points together.
Detects rare or unusual patterns in data, useful in fraud detection and quality control.
Techniques to process and analyze textual data, including tokenization, sentiment analysis, and language understanding.
Using ML to identify objects or features in images, often via deep learning or transfer learning.
Training a model involves feeding data and adjusting parameters; evaluation uses metrics like accuracy and F1-score.
Strategies like k-fold cross-validation ensure model generalization and avoid overfitting.
Adjusting model parameters to optimize performance using grid search, random search, or automated tools.
Models can be deployed as APIs, embedded in apps, or deployed on cloud services for scalable access.
ONNX provides interoperability between ML frameworks, allowing C# apps to consume models trained elsewhere.
Track model performance post-deployment and retrain with new data as needed to maintain accuracy.
Addressing bias, privacy, transparency, and fairness in AI model development and deployment.
Build web APIs or services that provide ML model predictions to client applications.
// ML.NET example for binary sentiment classification using System; using Microsoft.ML; using Microsoft.ML.Data; public class SentimentData { public string Text { get; set; } public bool Label { get; set; } } public class SentimentPrediction { [ColumnName("PredictedLabel")] public bool Prediction { get; set; } public float Probability { get; set; } public float Score { get; set; } } class Program { static void Main() { // Create ML context for pipeline operations var mlContext = new MLContext(); // Example training data var trainingData = new[] { new SentimentData { Text = "I love this product!", Label = true }, new SentimentData { Text = "This is the worst thing I bought.", Label = false }, new SentimentData { Text = "Amazing quality and great value.", Label = true }, new SentimentData { Text = "Not worth the money.", Label = false } }; // Load training data into IDataView var data = mlContext.Data.LoadFromEnumerable(trainingData); // Define data processing and trainer pipeline var pipeline = mlContext.Transforms.Text.FeaturizeText("Features", nameof(SentimentData.Text)) .Append(mlContext.BinaryClassification.Trainers.SdcaLogisticRegression(labelColumnName: nameof(SentimentData.Label), featureColumnName: "Features")); // Train the model var model = pipeline.Fit(data); // Create prediction engine for single predictions var predEngine = mlContext.Model.CreatePredictionEngine(model); // Predict sentiment of new sample text var sample = new SentimentData { Text = "I really like this!" }; var prediction = predEngine.Predict(sample); Console.WriteLine($"Text: {sample.Text}"); Console.WriteLine($"Prediction: {(prediction.Prediction ? "Positive" : "Negative")}"); Console.WriteLine($"Probability: {prediction.Probability:P2}"); } }
Predict continuous values by fitting a linear model to input data.
// Linear regression example (simplified) using System; class LinearRegressionExample { static void Main() { // Sample data points (x, y) double[] x = {1, 2, 3, 4, 5}; double[] y = {2, 4, 6, 8, 10}; // Calculate slope (m) and intercept (b) double m = 2; // From data: y = 2x (ideal case) double b = 0; // Predict y for a new x double x_new = 6; double y_pred = m * x_new + b; Console.WriteLine($"Predicted value at x={x_new}: {y_pred}"); } }
Classify data points into two categories using logistic regression.
// Simplified logistic regression example using System; class LogisticRegressionExample { static double Sigmoid(double z) => 1.0 / (1.0 + Math.Exp(-z)); static void Main() { double weight = 0.5; double bias = -1.0; double input = 3.0; double linear_output = weight * input + bias; double probability = Sigmoid(linear_output); Console.WriteLine($"Probability of class 1: {probability:F2}"); Console.WriteLine(probability > 0.5 ? "Class 1" : "Class 0"); } }
Group data points into clusters by minimizing within-cluster variance.
// Very simplified K-Means illustration using System; using System.Linq; class KMeansExample { static void Main() { // 2D points double[][] points = { new double[]{1, 2}, new double[]{1, 4}, new double[]{1, 0}, new double[]{10, 2}, new double[]{10, 4}, new double[]{10, 0} }; // Two cluster centers initial guess double[][] centroids = { new double[]{1,2}, new double[]{10,2} }; // Assign points to clusters (manually for brevity) Console.WriteLine("Points near centroid 1:"); foreach(var p in points.Where(p => Distance(p, centroids[0]) < Distance(p, centroids[1]))) Console.WriteLine($"({p[0]}, {p[1]})"); Console.WriteLine("Points near centroid 2:"); foreach(var p in points.Where(p => Distance(p, centroids[1]) <= Distance(p, centroids[0]))) Console.WriteLine($"({p[0]}, {p[1]})"); } static double Distance(double[] a, double[] b) => Math.Sqrt(Math.Pow(a[0] - b[0], 2) + Math.Pow(a[1] - b[1], 2)); }
Make classification decisions by splitting data according to features.
// Very basic decision tree logic (manual rules) using System; class DecisionTreeExample { static void Main() { string weather = "sunny"; bool play = weather == "sunny" || weather == "overcast"; Console.WriteLine($"Weather: {weather}, Play: {play}"); } }
Calculate output of a single-layer perceptron.
using System; class SimpleNN { static double Sigmoid(double x) => 1.0 / (1.0 + Math.Exp(-x)); static void Main() { double[] inputs = { 1.0, 0.5 }; double[] weights = { 0.9, -0.8 }; double bias = 0.1; double sum = 0; for (int i = 0; i < inputs.Length; i++) sum += inputs[i] * weights[i]; sum += bias; double output = Sigmoid(sum); Console.WriteLine($"Neural network output: {output:F3}"); } }
Split text into tokens (words) for processing.
using System; class TokenizationExample { static void Main() { string sentence = "Hello, AI world!"; char[] delimiters = { ' ', ',', '!' }; var tokens = sentence.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); Console.WriteLine("Tokens:"); foreach (var token in tokens) Console.WriteLine(token); } }
Analyze sentiment by counting positive/negative words.
using System; class SentimentAnalysisExample { static void Main() { string text = "I love this product, it is great!"; string[] positiveWords = { "love", "great", "good", "happy" }; string[] negativeWords = { "hate", "bad", "terrible", "sad" }; int score = 0; foreach (var word in text.ToLower().Split(' ')) { if (Array.Exists(positiveWords, w => w == word)) score++; if (Array.Exists(negativeWords, w => w == word)) score--; } string sentiment = score > 0 ? "Positive" : score < 0 ? "Negative" : "Neutral"; Console.WriteLine($"Sentiment: {sentiment}"); } }
Convert RGB pixel values to grayscale.
using System; class GrayscaleExample { static void Main() { // RGB pixel example byte r = 120, g = 200, b = 150; // Grayscale using luminosity method byte gray = (byte)(0.21 * r + 0.72 * g + 0.07 * b); Console.WriteLine($"Grayscale value: {gray}"); } }
Update Q-values for state-action pairs in a simplified environment.
using System; class QLearningExample { static void Main() { double[,] qTable = new double[2,2]; // states x actions int currentState = 0; int actionTaken = 1; double reward = 10; double learningRate = 0.1; double discountFactor = 0.9; double maxFutureQ = 5; // hypothetical max Q for next state // Q-learning update rule qTable[currentState, actionTaken] = qTable[currentState, actionTaken] + learningRate * (reward + discountFactor * maxFutureQ - qTable[currentState, actionTaken]); Console.WriteLine($"Updated Q-value: {qTable[currentState, actionTaken]:F2}"); } }
Load and run inference on an ONNX model in C# (outline only, requires packages).
// This is a conceptual outline, requires Microsoft.ML.OnnxRuntime package /* using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; var session = new InferenceSession("model.onnx"); // Prepare input tensor here // Run inference using var results = session.Run(inputData); var output = results.First().AsEnumerable().ToArray(); Console.WriteLine($"Model output: {string.Join(", ", output)}"); */
Generate text by combining fixed templates with variables.
using System; class NLGExample { static void Main() { string name = "Alice"; string mood = "happy"; string sentence = $"Hello, {name}! I am feeling {mood} today."; Console.WriteLine(sentence); } }
Map words to numeric vectors (hardcoded example).
using System; class WordEmbeddingExample { static void Main() { var embeddings = new System.Collections.Generic.Dictionary() { {"cat", new float[]{1.0f, 0.5f, 0.3f}}, {"dog", new float[]{0.9f, 0.6f, 0.4f}} }; float[] catVec = embeddings["cat"]; Console.WriteLine($"Embedding for 'cat': [{string.Join(", ", catVec)}]"); } }
Basic keyword matching to respond to user input.
using System; class ChatbotExample { static void Main() { Console.WriteLine("Ask me anything:"); string input = Console.ReadLine().ToLower(); if (input.Contains("hello")) Console.WriteLine("Hi there!"); else if (input.Contains("how are you")) Console.WriteLine("I'm an AI, feeling binary!"); else Console.WriteLine("I don't understand."); } }
Extract first sentence as summary (simplified).
using System; class TextSummarizationExample { static void Main() { string text = "Artificial intelligence is fascinating. It changes the world."; string[] sentences = text.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); string summary = sentences.Length > 0 ? sentences[0] + "." : ""; Console.WriteLine($"Summary: {summary}"); } }
Detect edges by comparing pixel differences (conceptual example).
// Conceptual pseudo-code - real implementation requires image libs /* For each pixel: Compare brightness to neighbor pixels If difference > threshold, mark as edge */
Check and correct common spelling mistakes.
using System; class SpellCheckerExample { static void Main() { string text = "Ths is an exmple."; var corrections = new System.Collections.Generic.Dictionary{ {"ths", "this"}, {"exmple", "example"} }; string[] words = text.Split(' '); for(int i=0; i < words.Length; i++) { string w = words[i].ToLower(); if(corrections.ContainsKey(w)) words[i] = corrections[w]; } string corrected = string.Join(" ", words); Console.WriteLine($"Corrected text: {corrected}"); } }
Choose action based on exploration/exploitation (simplified).
using System; class BanditExample { static Random rnd = new Random(); static void Main() { double epsilon = 0.1; // exploration rate double[] actionValues = { 1.0, 2.0, 3.0 }; // estimated values // Choose action int action = rnd.NextDouble() < epsilon ? rnd.Next(3) : ArgMax(actionValues); Console.WriteLine($"Chosen action: {action}"); } static int ArgMax(double[] array) { int bestIndex = 0; double max = array[0]; for (int i=1; imax) { max = array[i]; bestIndex = i; } } return bestIndex; } }
Recommend items based on user preferences using cosine similarity.
using System; class RecommendationExample { static double CosineSimilarity(double[] a, double[] b) { double dot = 0, magA = 0, magB = 0; for (int i = 0; i < a.Length; i++) { dot += a[i] * b[i]; magA += a[i] * a[i]; magB += b[i] * b[i]; } return dot / (Math.Sqrt(magA) * Math.Sqrt(magB)); } static void Main() { double[] userPrefs = { 1, 0, 1 }; double[][] items = { new double[]{1, 0, 0}, new double[]{0, 1, 1}, new double[]{1, 0, 1} }; int bestIndex = 0; double bestSim = -1; for (int i = 0; i < items.Length; i++) { double sim = CosineSimilarity(userPrefs, items[i]); if (sim > bestSim) { bestSim = sim; bestIndex = i; } } Console.WriteLine($"Recommended item index: {bestIndex}"); } }
Generate text by chaining previous word probabilities (conceptual snippet).
// Conceptual example - build Markov chain from sample text and generate reply /* 1. Parse text into word pairs 2. Store next-word probabilities 3. Generate sentences by sampling from probabilities */
Call Azure AI APIs for vision, speech, language, and decision making from C#.
// Pseudocode for calling Azure Cognitive Services SDK /* var client = new ComputerVisionClient(credentials); var result = await client.AnalyzeImageAsync(imageUrl); Console.WriteLine(result.Description.Captions[0].Text); */