C Sharp (C#) Tutorial


Beginners To Experts


The site is under development.

C# Tutorial

History and Overview of C#

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.

Setting Up Development Environment (Visual Studio/VS Code)

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.

Your First C# Program: Hello World

The classic first program outputs "Hello, World!" to the console. It demonstrates the structure of a basic C# program.

Understanding the Main Method

The Main method is the entry point of every C# console application. When the program runs, execution starts inside Main.

Variables and Data Types

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.

Basic Input and Output

Console.WriteLine() prints output to the console. Console.ReadLine() reads user input from the console.

Comments and Code Style

Comments explain code and are ignored during execution. Use // for single-line comments and /* ... */ for multi-line. Proper code style improves readability and maintainability.

Naming Conventions in C#

Use PascalCase for class names and method names. Use camelCase for local variables and parameters. Follow these conventions to keep code consistent and clear.

Understanding .NET and CLR

.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.

Running and Debugging C# Programs

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.

Code Example: Hello World and Basic Features

// 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: if, else, else if

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.

Switch Statements

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.

For Loops

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.

While Loops

A while loop executes its body repeatedly as long as the specified condition remains true. The condition is checked before each iteration.

Do-While Loops

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 and Continue Statements

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

Nested loops are loops inside loops. They are commonly used to process multi-dimensional data structures such as matrices.

Using goto (when and why to avoid)

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.

Short-circuit Evaluation

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.

Best Practices for Control Flow

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.

Code Example: Control Flow and Loops

// 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;
        }
    }
}
      

Defining Methods in C#

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.

Method Parameters and Arguments

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.

Return Types and Void Methods

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

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 and Named Parameters

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

Recursive methods are functions that call themselves. They must include a base condition to avoid infinite loops and stack overflows.

Passing Parameters by Value and by Reference (ref, out)

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 (C# 7+)

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 and Anonymous Methods

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

Expression-bodied members allow single-line methods or properties using => instead of full block syntax, making code shorter and cleaner when appropriate.

Code Example: Various Method Types

// 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
            Func subtract = (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;
    }
}
      

Understanding Classes and Objects

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 and Properties

Fields are variables declared in a class to hold data. Properties provide controlled access to those fields, often using get/set accessors.

Constructors and Destructors

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.

Method Overriding and Overloading

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 and Access Modifiers (public, private, etc.)

Encapsulation hides internal details of an object. Access modifiers like public, private, and protected control visibility.

The this Keyword

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 and Classes

Static members belong to the class rather than any instance. A static class cannot be instantiated and is often used for utility functions.

Object Initialization Syntax

C# allows initializing objects using object initializer syntax with curly braces to assign property values at creation.

Inheritance Basics

Inheritance lets one class (child) acquire properties and methods from another (parent). It promotes code reuse and organization.

Polymorphism Fundamentals

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.

Code Example: OOP Basics

// 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);
        }
    }
}
      

Abstract Classes and Methods

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 and Implementation

Interfaces define contracts with method signatures but no implementation. A class can implement multiple interfaces, promoting flexibility and decoupling.

Sealed Classes and Methods

A sealed class cannot be inherited. A sealed method prevents further overriding in derived classes. This is useful for securing functionality.

Virtual Methods and Overriding

A virtual method can be overridden in derived classes using the override keyword, allowing custom behavior while preserving polymorphism.

Method Hiding with new Keyword

When a derived class defines a method with the same name as one in its base class, the new keyword hides the base version.

Casting and Type Checking (is, as, typeof)

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

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

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).

Nested Types

A class, struct, or enum can be nested inside another class. Useful for logically grouping code and limiting scope.

Design Patterns Overview in C#

Design patterns are proven solutions to common software design problems. Examples include Singleton, Factory, Observer, and Strategy. They improve code structure, readability, and reusability.

Code Example: Advanced OOP Concepts

// 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 and Multidimensional Arrays

Arrays store fixed-size collections of elements of the same type. Multidimensional arrays (e.g., 2D arrays) are useful for matrix-like data.

Lists and List<T>

List<T> is a resizable, generic collection for storing elements. It's more flexible than arrays and supports dynamic resizing.

Dictionaries and Dictionary<TKey, TValue>

A Dictionary stores key-value pairs and allows fast lookups by key.

Sets and HashSet<T>

A HashSet<T> represents a collection of unique values with no duplicates.

Queues and Stacks

Queue is FIFO (first-in-first-out), while Stack is LIFO (last-in-first-out).

Working with Generic Methods and Classes

Generics allow methods and classes to operate on data types specified at runtime, promoting reusability and type safety.

IEnumerable and IEnumerator Interfaces

IEnumerable allows iteration over a collection. IEnumerator exposes the current element and supports manual iteration.

LINQ Basics with Collections

LINQ (Language Integrated Query) allows querying collections using a SQL-like syntax or lambda expressions for filtering, sorting, and projecting data.

ReadOnlyCollection and Immutable Collections

These collections ensure data cannot be modified once created, improving safety and predictability in concurrent or shared contexts.

Performance Considerations with Collections

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.

Code Example: Collections and Generics

// 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);
        }
    }
}
      

Understanding Exceptions

Exceptions are unexpected errors that occur during runtime. C# provides structured handling to catch and recover from exceptions without crashing the program.

Try, Catch, Finally Blocks

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.

Creating Custom Exceptions

You can define your own exception types by inheriting from Exception. This adds semantic clarity and custom behavior.

Throwing Exceptions

Use the throw keyword to manually raise an exception. This is useful for validation or enforcing business rules.

Using using Statement for Resource Management

The using statement ensures that disposable resources (like files or streams) are released automatically even if an exception occurs.

Exception Filters (C# 6+)

Exception filters let you conditionally catch exceptions using when clauses, enabling fine-grained control over exception handling.

Stack Traces and Debugging Exceptions

A stack trace shows the sequence of method calls that led to the exception, helping developers trace the error origin.

Best Practices for Exception Handling

Catch only expected exceptions, avoid swallowing exceptions, always log errors, and prefer specific exception types.

Handling Multiple Exceptions

You can handle different exceptions in separate catch blocks or use a single block for general exceptions when appropriate.

Logging Exceptions

Logging exceptions helps with diagnostics and post-mortem analysis. Use tools like Console, Trace, or logging frameworks.

Code Example: Exception Handling

// 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
            }
        }
    }
}
      

String Declaration and Initialization

Strings in C# are declared using the string keyword and initialized with double quotes. They are reference types and are stored on the heap.

String Immutability

Strings are immutable in C#, meaning their values cannot be changed after creation. Modifying a string creates a new object in memory.

String Methods (Substring, Replace, IndexOf, etc.)

Strings come with built-in methods like Substring, Replace, IndexOf, ToUpper, etc., for manipulation and searching.

String Interpolation and Formatting

Interpolation (using $"{value}") and formatting (like String.Format) make string construction dynamic and readable.

StringBuilder for Performance

StringBuilder is used for building dynamic strings efficiently, especially in loops, avoiding the cost of string immutability.

Regular Expressions in C#

Regular expressions (regex) help search and manipulate strings using patterns. The Regex class provides powerful tools for matching.

Parsing and Converting Strings

Strings can be parsed into numeric types using int.Parse, TryParse, or Convert.ToInt32.

Comparing Strings and Culture Info

Comparing strings can be case-sensitive or culture-aware using String.Compare, .Equals(), or CultureInfo.

Working with Spans and Memory (Advanced)

Span<T> and Memory<T> offer advanced, high-performance memory manipulation without allocations. They're used in performance-critical string slicing.

Localization and Resource Files

Localization involves using resource (.resx) files to support multiple languages. This enables internationalization of strings in an application.

Code Example: String Handling

// 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)
            ReadOnlySpan span = "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!");

        }
    }
}
      

What is a Delegate?

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.

Creating and Using Delegates

You declare a delegate type, then create an instance pointing to a method, and finally invoke it like a function.

Multicast Delegates

A multicast delegate can point to and invoke multiple methods using +=. All methods execute in order.

Events and Event Handlers

Events in C# use delegates under the hood and follow the observer pattern. They notify subscribers when an action occurs.

Event Keywords and Accessors

The event keyword declares an event. You can also create custom add/remove accessors to control how event handlers are managed.

Anonymous Methods

Anonymous methods are inline methods without names, defined using the delegate keyword, and useful for quick event handling.

Lambda Expressions Syntax

Lambda expressions use the => operator to define inline anonymous functions, making code cleaner and more concise.

Func, Action, and Predicate Delegates

Func represents a method that returns a value. Action is for methods that return void. Predicate is a delegate that returns a boolean.

Capturing Variables in Lambdas

Lambdas can "capture" variables from their outer scope, allowing them to access local variables even after the method returns.

Event-driven Programming Concepts

Event-driven programming responds to events like button clicks or timers. Delegates and events are foundational to building responsive, interactive apps.

Code Example: Delegates, Events, and Lambdas

// 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();
        }
    }
}
      

Introduction to LINQ

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

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.

Filtering and Projection (Where, Select)

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).

Ordering and Grouping Data

OrderBy() and OrderByDescending() sort data, while GroupBy() categorizes elements into groups based on a key.

Joining Collections

Join() performs inner joins between two collections based on matching keys. GroupJoin() performs left joins.

Quantifiers (Any, All)

Any() checks if any elements satisfy a condition. All() checks if all elements satisfy a condition.

Aggregation Operators (Sum, Count, Average)

LINQ provides built-in aggregation methods like Sum(), Count(), Min(), Max(), and Average() to compute values across a sequence.

Deferred Execution vs Immediate Execution

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 and LINQ to SQL Overview

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.

Writing Custom LINQ Operators

Custom LINQ operators can be implemented using IEnumerable and yield return, enabling you to define reusable query behaviors for specialized needs.

Code Example: LINQ in Action

// 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;
            }
        }
    }
}
      

Working with File and Directory Classes

The System.IO.File and Directory classes provide static methods to perform operations like creating, copying, moving, and deleting files or directories.

Reading and Writing Text Files

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.

Working with Binary Files

Use BinaryReader and BinaryWriter to read/write binary data (e.g., images, executables, serialized objects).

Streams and StreamReader/StreamWriter

Stream is the base class for reading/writing bytes. StreamReader and StreamWriter provide higher-level access to read/write text using streams.

FileSystemWatcher for Monitoring Changes

FileSystemWatcher monitors a folder and raises events when files are created, changed, deleted, or renamed. It’s useful for building reactive systems.

JSON Serialization and Deserialization

System.Text.Json and Newtonsoft.Json allow converting objects to JSON (serialization) and back (deserialization). This is commonly used in APIs and configuration files.

XML Serialization

XmlSerializer converts objects to XML format and reads XML back into objects. It’s useful for config files and interoperable data.

Working with CSV Files

CSVs are plain text files with comma-separated values. They are easy to read/write using string.Split() or libraries like CsvHelper.

Async File I/O Operations

Use ReadAllTextAsync(), WriteAllTextAsync(), and Stream with async to perform non-blocking file operations.

Best Practices for File Handling

Always close or dispose streams using using blocks. Handle exceptions, use async for scalability, and avoid hardcoded paths by using Path.Combine().

Code Example: File I/O and Serialization

// 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();
        }
    }
}
      

Synchronous vs Asynchronous Programming

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.

Using Tasks and Task<T>

The Task and Task<T> classes represent asynchronous operations. They allow you to run code in parallel threads and return results upon completion.

Async and Await Keywords

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.

Creating and Cancelling Tasks

You can start tasks using Task.Run(). To cancel them safely, use a CancellationTokenSource and pass its token to the task logic.

Task Continuations and Chaining

You can chain tasks using .ContinueWith() or use await in sequence to create dependent workflows.

Parallel Programming with Parallel Class

The Parallel class (e.g., Parallel.For()) allows running multiple tasks simultaneously, utilizing multi-core processors effectively.

Thread Safety and Synchronization

When multiple threads access shared resources, thread safety must be enforced to avoid race conditions and data corruption.

Using lock and Other Synchronization Primitives

The lock keyword ensures that only one thread can access a critical section at a time. Other options include Mutex, Semaphore, and Monitor.

Deadlocks and How to Avoid Them

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.

Best Practices for Async Code

- 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.

Code Example: Async Programming

// 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;
                }
            }
        }
    }
}
      

Introduction to ADO.NET

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.

Connecting to SQL Server

Connections are established using SqlConnection with a connection string specifying the server, database, and credentials.

Executing Commands and Queries

Use SqlCommand to execute SQL statements or stored procedures, including SELECT, INSERT, UPDATE, and DELETE commands.

Using DataReaders and DataSets

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 to Prevent SQL Injection

Parameterized queries use placeholders for parameters in SQL commands, protecting against SQL injection by separating code from data.

Using Entity Framework Core (EF Core)

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 vs Database-First Approaches

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

LINQ to Entities allows querying the database using LINQ syntax on entity objects, providing compile-time checking and IntelliSense.

Migrations and Updating Database Schemas

Migrations help evolve the database schema as the model changes, enabling version control and incremental updates.

Working with Transactions

Transactions group multiple database operations into a single unit of work, ensuring all succeed or fail together, preserving data integrity.

Code Example: Database Access with ADO.NET and EF Core

// 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 DbSet Products { 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);
                }
            }
        }
    }
}
      

Introduction to Windows Forms

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.

Creating a Basic Form Application

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: Buttons, TextBoxes, Labels

Controls are UI elements the user interacts with. Button triggers actions, TextBox collects input, and Label displays text.

Event Handling in Windows Forms

Events notify the app when users interact, e.g., clicking a button. Event handlers are methods wired to these events to define responses.

Layout Management

Layout controls such as FlowLayoutPanel and TableLayoutPanel help arrange child controls dynamically and responsively.

Dialogs and Message Boxes

Use dialogs like OpenFileDialog or SaveFileDialog to interact with the file system, and MessageBox to show alerts and confirmations.

Menus and Toolbars

Menus (MenuStrip) provide navigation and commands; toolbars (ToolStrip) offer quick access buttons with icons.

Data Binding in Windows Forms

Data binding connects UI controls to data sources like databases or collections, automating data display and updates.

Working with Graphics and Drawing

The Graphics class allows drawing shapes, text, and images on forms and controls for custom UI and visualization.

Deploying Windows Forms Applications

After building your app, you package it with dependencies and distribute it via installers or self-contained executables.

Code Example: Simple Windows Forms App

// 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());
        }
    }
}
      

Introduction to ASP.NET Core

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.

Setting Up an ASP.NET Core Project

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 Pipeline

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 and Endpoints

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 and 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 and Razor Syntax

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 and Validation

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.

Dependency Injection Basics

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 and Environment Variables

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.

Logging in ASP.NET Core

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.

Code Example: Basic ASP.NET Core Setup with Controller and Logging

// 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 and Authorization

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.

Working with Entity Framework Core in ASP.NET

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.

Building REST APIs

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.

Using Swagger/OpenAPI

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.

Middleware Customization

Custom middleware components allow inserting your own logic into the request pipeline. Middleware can modify requests/responses, handle errors, and add headers.

Caching Strategies

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 for Real-time Web Apps

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.

Global Exception Handling

Centralized error handling middleware captures unhandled exceptions. It enables consistent logging, user-friendly error responses, and improved app stability.

Health Checks and Monitoring

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.

Deployment to Azure and IIS

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.

Code Example: Adding Swagger and Simple Custom Middleware

// 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();
      

Introduction to Design Patterns

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.

Singleton Pattern

Ensures a class has only one instance and provides a global access point. Commonly used for logging, configuration, or managing shared resources.

Factory Pattern

Creates objects without exposing the instantiation logic to the client. Allows selecting the type of object to create at runtime, improving flexibility.

Observer Pattern

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.

Decorator Pattern

Adds behavior to objects dynamically without altering their class. Enables flexible and reusable code by wrapping objects with additional functionality.

Strategy Pattern

Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Lets the algorithm vary independently from clients that use it.

Repository Pattern

Provides a layer to abstract data access logic. Helps decouple business logic from data storage details and supports unit testing.

Dependency Injection Pattern

Injects dependencies into a class rather than having the class create them. Promotes loose coupling, easier testing, and better modularity.

MVC Pattern Overview

Separates application logic into Model (data), View (UI), and Controller (input logic). Improves organization and supports parallel development and testing.

Using Patterns in Real Projects

Applying patterns appropriately leads to maintainable and scalable applications. Combining multiple patterns is common in enterprise software development.

Code Example: Singleton and Factory Patterns

// 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 Basics

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.

Using MSTest Framework

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 Introduction

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.

Writing Test Cases and Assertions

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 Dependencies (Moq)

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 Testing

Integration tests verify that multiple components or systems work together correctly. They typically involve databases, web services, or other external dependencies.

Test-Driven Development (TDD)

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

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.

Continuous Integration for Tests

CI systems automatically run tests on each code check-in to detect issues early. Popular CI tools include GitHub Actions, Azure DevOps, and Jenkins.

Best Practices for Testing

- 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.

Code Example: MSTest with Moq Mocking

// 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);
        }
    }
}
      

Consuming REST APIs with HttpClient

HttpClient is a powerful class to send HTTP requests and receive responses from RESTful APIs. It supports asynchronous calls, headers, and JSON/XML serialization.

Creating RESTful Services in C#

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 Services Overview

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.

Working with JSON and XML Data

APIs often use JSON or XML to exchange data. .NET provides libraries like System.Text.Json and XmlSerializer for serializing and deserializing these formats.

Authentication with APIs (OAuth, JWT)

Secure APIs require authentication. OAuth provides delegated access, while JWT (JSON Web Tokens) are widely used for stateless token-based authentication.

API Versioning Strategies

Versioning ensures backward compatibility as APIs evolve. Strategies include URL versioning, query parameters, headers, or media type versioning.

Handling Rate Limiting and Throttling

To prevent abuse and overload, APIs implement rate limiting. Clients should handle HTTP 429 responses and retry after a specified time.

API Documentation with Swagger

Swagger (OpenAPI) generates interactive API documentation. It helps developers understand endpoints, request/response formats, and test APIs directly.

Using gRPC with C#

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.

Error Handling in API Calls

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.

Code Example: Consuming a REST API with HttpClient

// 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 and Attributes

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.

Dynamic Types and ExpandoObject

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.

Memory Management and Garbage Collection

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 and Pointers

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.

Performance Profiling and Optimization

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.

Using Span<T> and Memory<T>

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 and Roslyn API

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.

Working with Multithreading

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.

Security Best Practices in C#

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.

Preparing for C# Certifications

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.

Code Example: Using Reflection and ExpandoObject

// 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());
    }
}
      

Introduction to Serialization and Deserialization

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.

Using System.Text.Json for JSON Operations

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.

Configuring JSON Serialization Options

You can customize serialization behavior using JsonSerializerOptions, including property naming policies, ignoring null values, and controlling case sensitivity.

Handling Complex Types and Nested Objects

Serialization can handle nested objects and collections, converting the entire object graph into JSON. Proper handling ensures data integrity and correct deserialization.

Custom Converters for JSON

Custom converters allow you to control how specific types are serialized and deserialized. This is useful for complex types or non-standard JSON formats.

Ignoring Properties During Serialization

Attributes like [JsonIgnore] let you exclude properties from serialization to protect sensitive data or reduce payload size.

Working with Newtonsoft.Json (Json.NET)

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.

Serializing Collections and Dictionaries

Collections like lists, arrays, and dictionaries serialize naturally into JSON arrays or objects, preserving their structure.

Performance Considerations in Serialization

Performance depends on object complexity, size, and library choice. System.Text.Json is faster for most cases, but Newtonsoft.Json offers more features.

Securing Serialized Data

Serialized data can contain sensitive information. Ensure data is encrypted or sanitized as needed before storage or transmission.

Code Example: JSON Serialization and Deserialization with System.Text.Json

// 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));
    }
}
      

Overview of RESTful Web APIs

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.

Creating Web APIs with ASP.NET Core

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 and Attribute Routing in APIs

Routing maps HTTP requests to controller actions. Attribute routing enables decorating controllers and actions with route templates for flexible URL patterns.

Model Binding and Validation in APIs

Model binding automatically maps request data (query, body, headers) to action parameters. Validation attributes ensure data integrity, returning errors when invalid input is detected.

Returning HTTP Status Codes and Results

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.

Versioning Web APIs

API versioning prevents breaking changes by supporting multiple versions concurrently. Versions can be specified via URL segments, query strings, or headers.

Securing APIs with Authentication and Authorization

Protect APIs using authentication schemes (JWT, OAuth2) and authorization policies to restrict access based on roles or claims.

Consuming APIs using HttpClient

HttpClient allows C# apps to send HTTP requests to APIs and process responses asynchronously.

Using Swagger for API Documentation

Swagger (OpenAPI) auto-generates interactive documentation, letting developers explore and test API endpoints.

Testing Web APIs with Postman and Integration Tests

Postman is a popular tool for manual API testing. Automated integration tests verify end-to-end API functionality during development.

Code Example: Simple ASP.NET Core Web API Controller

// 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; }
    }
}
      

Introduction to Entity Framework Core

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.

Configuring the DbContext and Models

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.

CRUD Operations with EF Core

EF Core supports creating, reading, updating, and deleting (CRUD) operations via LINQ queries and DbSet methods such as Add, Find, Update, and Remove.

Querying Data with LINQ and EF Core

LINQ queries let you filter, sort, and project data from your models. EF Core translates these LINQ expressions into optimized SQL queries.

Migrations: Creating and Applying Database Changes

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.

Relationships: One-to-One, One-to-Many, Many-to-Many

EF Core supports defining relationships between models using navigation properties and data annotations or Fluent API. Proper configuration ensures referential integrity and cascading behaviors.

Eager, Lazy, and Explicit Loading

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 Tuning and Query Optimization

Performance can be improved by optimizing queries, limiting data loaded, using projections, and caching. Profiling tools help identify slow queries.

Transactions and Concurrency Control

EF Core supports database transactions to group operations atomically. Concurrency control prevents conflicting data updates using optimistic or pessimistic locking.

Using Raw SQL Queries in EF Core

When needed, raw SQL queries can be executed using FromSqlRaw or ExecuteSqlRaw for complex or performance-critical scenarios.

Code Example: Basic EF Core DbContext and CRUD

// 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 DbSet Products { 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}");
        }
    }
}
      

Understanding Common Security Threats

Awareness of common security threats like injection attacks, data breaches, and unauthorized access is essential. Understanding attack vectors helps in designing secure applications.

Secure Password Storage and Hashing

Passwords should never be stored in plain text. Use strong cryptographic hashing algorithms (e.g., bcrypt, PBKDF2) with salts to securely store passwords.

Using HTTPS and SSL/TLS in Applications

Secure communication by enforcing HTTPS and SSL/TLS protocols to encrypt data transmitted between clients and servers.

Input Validation and Sanitization

Always validate and sanitize user inputs to prevent malicious data from causing harm such as injection or cross-site scripting.

Protecting Against SQL Injection

Use parameterized queries or ORM tools to avoid SQL injection vulnerabilities, which allow attackers to manipulate database queries.

Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF)

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.

Implementing Role-Based and Claims-Based Authorization

Restrict access based on user roles or claims, ensuring users can only access permitted resources or operations.

Using Identity Framework for Authentication

ASP.NET Core Identity provides robust user management, including authentication, password recovery, and multi-factor authentication.

Secure Configuration Management

Protect sensitive configuration settings like connection strings or API keys using secure storage options such as Azure Key Vault or environment variables.

Security Audits and Code Analysis Tools

Use automated tools for static code analysis, vulnerability scanning, and penetration testing to detect and fix security issues early.

Code Example: Secure Password Hashing with PBKDF2

// 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);
    }
}
      

Introduction to Windows Presentation Foundation (WPF)

WPF is a UI framework for building desktop client applications on Windows. It uses DirectX for rendering and supports rich graphics, controls, and multimedia.

Understanding XAML and Data Binding

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.

Controls and Layout Panels

WPF provides a variety of controls (buttons, text boxes, lists) and layout panels (Grid, StackPanel, DockPanel) to arrange UI elements responsively.

MVVM (Model-View-ViewModel) Pattern Basics

MVVM is a design pattern that separates UI (View), business logic (ViewModel), and data (Model), facilitating testability and maintainability.

Commands and Routed Events

Commands encapsulate actions triggered by UI elements. Routed events travel up or down the visual tree, enabling flexible event handling.

Styles, Templates, and Themes

Styles define reusable property sets, templates control the visual structure of controls, and themes provide consistent look and feel across the app.

Animations and Visual States

WPF supports rich animations and visual state management to create interactive, visually appealing applications.

Working with Resources and Localization

Resources such as brushes and styles can be reused app-wide. Localization enables apps to support multiple languages and cultures.

Debugging and Performance Optimization in WPF

Tools like Visual Studio's WPF Inspector help debug UI. Performance tips include virtualization, deferred loading, and minimizing layout passes.

Packaging and Deployment of WPF Applications

WPF apps can be packaged using ClickOnce or MSIX for easy deployment and updates.

Code Example: Simple WPF MVVM Example with Data Binding

// 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;
        }
    }
}
      

Basics of Networking in C#

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.

Working with TCP and UDP Clients and Servers

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.

Using HttpClient for HTTP Communication

The HttpClient class allows sending HTTP requests and receiving responses asynchronously, making it suitable for REST API consumption.

Implementing WebSockets in C#

WebSockets enable full-duplex communication channels over a single TCP connection, useful for real-time apps. .NET provides WebSocket support via System.Net.WebSockets.

Building REST Clients and Services

REST clients use HttpClient, while REST services can be built with ASP.NET Core Web API. Both follow REST principles for resource manipulation.

Using gRPC with C#

gRPC is a high-performance, open-source RPC framework that uses Protocol Buffers for serialization. .NET supports gRPC to build efficient client-server communication.

Asynchronous Networking Patterns

Async-await patterns improve responsiveness in network programming by not blocking threads while waiting for network I/O.

Handling Network Errors and Timeouts

Proper error handling includes catching exceptions, retry policies, and managing timeouts to avoid hanging connections.

Secure Communication with SSL/TLS

Secure sockets layer (SSL) and Transport Layer Security (TLS) encrypt data transmitted over the network, protecting against eavesdropping and tampering.

Network Performance Tuning

Optimizations include connection pooling, minimizing data sent, compression, and efficient socket management to improve throughput and reduce latency.

Code Example: Simple TCP Client and Server

// 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 Overview

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.

Designing Microservices with C# and ASP.NET Core

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 and API Gateway

Service discovery helps locate microservices dynamically at runtime. API gateways act as single entry points that route requests, provide authentication, and aggregate responses.

Communication Between Microservices (REST, gRPC, Messaging)

Microservices communicate via HTTP REST APIs, gRPC for high performance, or asynchronous messaging systems like RabbitMQ or Kafka.

Data Management and Event Sourcing

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 and Saga Pattern

Distributed transactions span multiple microservices and can be handled using the Saga pattern, which coordinates local transactions through compensation actions.

Containerizing Microservices with Docker

Docker packages microservices into containers for consistent deployment environments across machines and clouds.

Using Kubernetes for Orchestration

Kubernetes automates deployment, scaling, and management of containerized microservices, handling service discovery, load balancing, and fault tolerance.

Monitoring and Logging Microservices

Centralized logging, metrics collection, and tracing tools like Prometheus and Jaeger help monitor health and diagnose issues.

Securing Microservices

Security involves authentication, authorization, encryption, and secure communication between services, often using OAuth2 and JWT tokens.

Code Example: Minimal ASP.NET Core Microservice

// 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; }
}
      

Introduction to Reactive Programming

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.

Observables and Observers

An Observable produces data or events, while an Observer subscribes to receive those events. Observers implement the IObserver<T> interface.

Creating and Subscribing to Observables

Observables can be created from various sources such as events, tasks, or collections. Subscribing starts the data flow to observers.

Operators for Filtering and Transforming Streams

Operators like Where, Select, Take, and Skip allow filtering and mapping the data streams.

Combining and Merging Streams

Streams can be combined using operators like Merge, Concat, and Zip to create complex event flows.

Error Handling in Reactive Streams

Rx.NET provides mechanisms to handle errors gracefully, such as Catch and Retry.

Scheduling and Threading with Rx

Scheduling controls the execution context of observables, enabling concurrency and thread switching with schedulers.

Using Subjects and Hot vs Cold Observables

Subjects act as both observers and observables. Cold observables produce data on subscription, while hot observables produce data regardless of subscriptions.

Practical Applications of Rx.NET

Rx.NET is useful for UI event handling, real-time data processing, asynchronous workflows, and more.

Testing Reactive Code

Testing involves verifying emitted sequences, timings, and side effects using tools like TestScheduler.

Code Example: Simple Observable and Subscription

// 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();
    }
}
      

Overview of Game Development in C#

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.

Introduction to Unity and C# Scripting

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.

Game Loop and Frame Updates

The game loop repeatedly updates game state and renders frames. Unity provides methods like Update() and FixedUpdate() for frame and physics updates.

Handling User Input

Unity's Input class captures keyboard, mouse, touch, and controller input to interact with games.

Working with 2D and 3D Graphics

Unity supports both 2D and 3D graphics, providing tools for sprites, meshes, materials, and shaders.

Physics and Collision Detection

Unity includes a physics engine with rigidbodies and colliders to simulate real-world physics and detect collisions.

Audio and Animation in Games

Sound effects and music can be played via AudioSources, while animations are managed with the Animator component and animation clips.

Scene Management and UI Design

Scenes are individual game levels or screens. Unity's UI system allows building menus, HUDs, and interactive elements.

Debugging and Performance Optimization

Unity provides debugging tools and profilers to optimize game performance and fix bugs.

Publishing and Deployment of Games

Games can be published to multiple platforms including PC, consoles, mobile, and web using Unity’s build system.

Code Example: Simple Player Movement Script in Unity

// 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);
    }
}
      

1. Introduction to Machine Learning

Machine Learning (ML) allows computers to learn patterns from data and make predictions or decisions without explicit programming.

2. ML.NET Framework Overview

ML.NET is a powerful open-source ML library for .NET developers, providing tools to build, train, and deploy models using C#.

3. Data Preparation and Cleaning

Data preparation involves cleaning and transforming raw data into a format suitable for machine learning models.

4. Feature Engineering

Feature engineering is creating new input variables from raw data to improve model accuracy and learning.

5. Data Normalization and Scaling

Techniques like min-max scaling and z-score normalization adjust features to a common scale for better training performance.

6. Handling Imbalanced Datasets

Methods such as oversampling and undersampling balance datasets where one class dominates, improving model fairness.

7. Supervised Learning Algorithms

Includes classification and regression techniques where models learn from labeled data to predict outcomes.

8. Unsupervised Learning and Clustering

Clustering algorithms find patterns in unlabeled data by grouping similar data points together.

9. Anomaly Detection

Detects rare or unusual patterns in data, useful in fraud detection and quality control.

10. Natural Language Processing (NLP)

Techniques to process and analyze textual data, including tokenization, sentiment analysis, and language understanding.

11. Image Classification and Computer Vision

Using ML to identify objects or features in images, often via deep learning or transfer learning.

12. Model Training and Evaluation

Training a model involves feeding data and adjusting parameters; evaluation uses metrics like accuracy and F1-score.

13. Cross-Validation Techniques

Strategies like k-fold cross-validation ensure model generalization and avoid overfitting.

14. Hyperparameter Tuning

Adjusting model parameters to optimize performance using grid search, random search, or automated tools.

15. Model Deployment Options

Models can be deployed as APIs, embedded in apps, or deployed on cloud services for scalable access.

16. Using ONNX Models in C#

ONNX provides interoperability between ML frameworks, allowing C# apps to consume models trained elsewhere.

17. Monitoring and Updating Models

Track model performance post-deployment and retrain with new data as needed to maintain accuracy.

18. Ethical AI Considerations

Addressing bias, privacy, transparency, and fairness in AI model development and deployment.

19. Integrating ML with ASP.NET Core

Build web APIs or services that provide ML model predictions to client applications.

20. Simple Binary Classification Example in ML.NET

// 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}");
    }
}
      

1. Simple Linear Regression

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}");
    }
}
      

2. Binary Classification with Logistic Regression

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");
    }
}
      

3. K-Means Clustering

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));
}
      

4. Decision Tree Classifier (Simple Example)

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}");
    }
}
      

5. Simple Neural Network Forward Pass

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}");
    }
}
      

6. Text Tokenization

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);
    }
}
      

7. Sentiment Analysis (Simple Rule-Based)

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}");
      }
}
      

8. Simple Image Processing (Grayscale Conversion)

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}");
    }
}
      

9. Reinforcement Learning Concept: Q-Table Update

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}");
    }
}
      

10. Using Pretrained ONNX Model (Outline)

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)}");
*/
      

11. Natural Language Generation (Simple Template)

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);
    }
}
      

12. Word Embedding Concept (Simplified)

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)}]");
    }
}
      

13. Simple Chatbot Logic

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.");
    }
}
      

14. Text Summarization Concept

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}");
    }
}
      

15. Image Edge Detection (Simplified Concept)

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
*/
      

16. AI-powered Spell Checker (Simple)

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}");
    }
}
      

17. Reinforcement Learning - Simple Bandit Algorithm

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; i max)
            {
                max = array[i];
                bestIndex = i;
            }
        }
        return bestIndex;
    }
}
      

18. AI-based Recommendation System (Simplified)

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}");
      }
}
      

19. AI Chatbot with Simple Markov Chain

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
*/
      

20. Integration with Azure Cognitive Services (Conceptual)

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);
*/