MODULE 1: OBJECT-ORIENTED PROGRAMMING - Complete Guide

11. Classes and Objects (The Foundation)

// CLASS = Blueprint/Template for creating objects
// OBJECT = Instance of a class (actual thing in memory)

// Simple class definition
public class Car
{
// Fields (data)
public string color;
public string model;
public int year;
}

// Creating and using objects
class Program
{
static void Main()
{
// Create object (instance) of Car class
Car myCar = new Car(); // 'new' keyword creates object in memory
// Set field values
myCar.color = "Red";
myCar.model = "Tesla Model 3";
myCar.year = 2024;
// Access field values
Console.WriteLine($"Car: {myCar.color} {myCar.model} {myCar.year}");
// Create multiple objects
Car car2 = new Car();
car2.color = "Blue";
car2.model = "Honda Civic";
car2.year = 2023;
Car car3 = new Car();
car3.color = "Black";
car3.model = "Ford Mustang";
car3.year = 2022;
}
}

Interview Question: What's the difference between a class and an object?

Answer: A class is a blueprint/template, an object is an instance created from that blueprint. Class exists in code, object exists in memory at runtime.

12. Fields and Properties (Data Encapsulation)

public class Customer
{
// FIELDS - Private data storage (encapsulation)
private string _name;
private int _age;
private decimal _salary;
private List<string> _orders = new List<string>();
// AUTO-PROPERTIES - Compiler creates hidden field (C# 3.0+)
public int Id { get; set; } // Read/write
public string Email { get; private set; } // Read only outside class
public DateTime CreatedDate { get; init; } // Can only set during init (C# 9+)
// FULL PROPERTIES - With logic
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("Name cannot be empty");
_name = value.Trim();
}
}
// Computed property (no backing field)
public string DisplayName => $"{_name} (ID: {Id})";
// Property with logic in getter
public int Age
{
get { return _age; }
set
{
if (value < 0 || value > 150)
throw new ArgumentException("Invalid age");
_age = value;
}
}
// Read-only property (computed each time)
public bool IsAdult => Age >= 18;
// Static property (shared across all instances)
public static string CompanyName { get; set; } = "Acme Inc.";
// Constructor to set init-only property
public Customer(string email)
{
Email = email;
CreatedDate = DateTime.Now;
}
}

// Usage
Customer.CompanyName = "Tech Corp"; // Set static property

var customer = new Customer("john@example.com");
customer.Id = 1;
customer.Name = "John Doe";
customer.Age = 30;
// customer.Email = "new@email.com"; // ERROR! Private setter
// customer.CreatedDate = DateTime.Now; // ERROR! Init-only property

Console.WriteLine(customer.DisplayName); // "John Doe (ID: 1)"

Interview Question: When would you use a field vs a property?

Answer: Always use properties for public access. Fields should be private. Properties allow validation, logic, and maintain binary compatibility if logic changes later.

13. Methods (Behavior)

public class Calculator
{
// BASIC METHOD
public int Add(int a, int b)
{
return a + b;
}
// VOID METHOD (no return)
public void PrintResult(int result)
{
Console.WriteLine($"Result: {result}");
}
// METHOD OVERLOADING (same name, different parameters)
public int Add(int a, int b, int c)
{
return a + b + c;
}
public double Add(double a, double b)
{
return a + b;
}
// STATIC METHOD (call without instance)
public static double Multiply(double a, double b)
{
return a * b;
}
// EXPRESSION-BODIED METHOD (C# 6+)
public int Subtract(int a, int b) => a - b;
// METHOD WITH OPTIONAL PARAMETERS
public int Power(int baseNum, int exponent = 2)
{
return (int)Math.Pow(baseNum, exponent);
}
// METHOD WITH PARAMS KEYWORD (variable arguments)
public int Sum(params int[] numbers)
{
int total = 0;
foreach (int num in numbers)
total += num;
return total;
}
}

// Usage
Calculator calc = new Calculator();
int sum1 = calc.Add(5, 3); // 8
int sum2 = calc.Add(5, 3, 2); // 10 (overloaded)
double sum3 = calc.Add(5.5, 3.2); // 8.7 (overloaded)
int power1 = calc.Power(3); // 9 (exponent optional)
int power2 = calc.Power(3, 3); // 27 (exponent provided)
int total = calc.Sum(1, 2, 3, 4, 5); // 15 (params)

// Static method call (no instance needed)
double product = Calculator.Multiply(5, 3); // 15

14. Constructors and Destructors

public class Employee
{
// Fields
private static int _nextId = 1;
private readonly int _id;
// DEFAULT CONSTRUCTOR (parameterless)
public Employee()
{
_id = _nextId++;
Name = "New Employee";
HireDate = DateTime.Now;
Console.WriteLine($"Employee {_id} created");
}
// PARAMETERIZED CONSTRUCTOR
public Employee(string name) : this() // Call default constructor first
{
Name = name;
}
// CONSTRUCTOR WITH MULTIPLE PARAMETERS
public Employee(string name, decimal salary) : this(name)
{
Salary = salary;
}
// STATIC CONSTRUCTOR (called once, before first instance)
static Employee()
{
CompanyName = "Global Corp";
Console.WriteLine("Static constructor called");
}
// PRIVATE CONSTRUCTOR (cannot create instance outside class)
private Employee(string secretCode)
{
// Used for factory pattern
}
// PRIMARY CONSTRUCTOR (C# 12+)
// public class Product(string name, decimal price) { }
// Properties
public string Name { get; set; }
public decimal Salary { get; set; }
public DateTime HireDate { get; set; }
public static string CompanyName { get; private set; }
// DESTRUCTOR/FINALIZER (rarely used, called by garbage collector)
~Employee()
{
Console.WriteLine($"Employee {_id} destroyed");
// Cleanup unmanaged resources (files, connections, etc.)
}
// Factory method using private constructor
public static Employee CreateWithCode(string code)
{
return new Employee(code);
}
}

// Usage
Console.WriteLine("Creating first employee...");
Employee emp1 = new Employee(); // Calls default
Employee emp2 = new Employee("John Doe"); // Calls parameterized
Employee emp3 = new Employee("Jane Smith", 50000); // Calls both constructors

Console.WriteLine($"Company: {Employee.CompanyName}");
Console.WriteLine($"Employee: {emp2.Name}");

// Different ways to create objects
// 1. Constructor
Employee emp4 = new Employee("Alice");

// 2. Object initializer (after constructor)
Employee emp5 = new Employee
{
Name = "Bob",
Salary = 60000,
HireDate = DateTime.Now
};

// 3. Factory method
Employee emp6 = Employee.CreateWithCode("SECRET123");

Constructor Chaining Example:

public class Person
{
public string Name { get; }
public int Age { get; }
public string City { get; }
// Primary constructor that does all the work
public Person(string name, int age, string city)
{
Name = name;
Age = age;
City = city;
}
// Chain to main constructor with default city
public Person(string name, int age) : this(name, age, "Unknown")
{
}
// Chain with default age and city
public Person(string name) : this(name, 0, "Unknown")
{
}
}

// Usage
Person p1 = new Person("John", 30, "New York");
Person p2 = new Person("Jane", 25); // City = "Unknown"
Person p3 = new Person("Bob"); // Age = 0, City = "Unknown"

15. Access Modifiers

// AccessModifiersExample.cs
using System;

namespace AccessModifiers
{
// PUBLIC - Accessible anywhere
public class PublicClass
{
public int publicField = 10;
private int privateField = 20;
protected int protectedField = 30;
internal int internalField = 40;
protected internal int protectedInternalField = 50;
private protected int privateProtectedField = 60;
public void Demo()
{
// All accessible within same class
Console.WriteLine(publicField);
Console.WriteLine(privateField);
Console.WriteLine(protectedField);
Console.WriteLine(internalField);
Console.WriteLine(protectedInternalField);
Console.WriteLine(privateProtectedField);
}
}
// INTERNAL - Only accessible within same assembly/project
internal class InternalClass
{
public void Test() { }
}
// Derived class in same assembly
public class DerivedClass : PublicClass
{
public void AccessBaseMembers()
{
publicField = 1; // ✓ OK
// privateField = 2; // ✗ Error - private
protectedField = 3; // ✓ OK - accessible in derived class
internalField = 4; // ✓ OK - same assembly
protectedInternalField = 5; // ✓ OK
privateProtectedField = 6; // ✓ OK - same assembly AND derived
}
}
}

// Different assembly
namespace OtherAssembly
{
using AccessModifiers;
public class OtherClass
{
public void Test()
{
PublicClass obj = new PublicClass();
obj.publicField = 1; // ✓ OK
// obj.privateField = 2; // ✗ Error
// obj.protectedField = 3; // ✗ Error (not derived)
// obj.internalField = 4; // ✗ Error (different assembly)
// obj.protectedInternalField = 5; // ✓ OK? NO - different assembly, not derived
// obj.privateProtectedField = 6; // ✗ Error
}
}
public class DerivedFromOtherAssembly : PublicClass
{
public void AccessBase()
{
publicField = 1; // ✓ OK
// privateField = 2; // ✗ Error
protectedField = 3; // ✓ OK
// internalField = 4; // ✗ Error (different assembly)
protectedInternalField = 5; // ✓ OK (protected part works)
// privateProtectedField = 6; // ✗ Error (requires same assembly)
}
}
}

Access Modifiers Summary Table:







public

private

protected

internal

protected internal

private protected

16. Static vs Instance Members

public class BankAccount
{
// INSTANCE MEMBERS - Each object has its own copy
public string AccountNumber { get; set; }
public decimal Balance { get; private set; }
// STATIC MEMBERS - Shared across ALL instances
private static decimal _interestRate = 0.02m;
private static int _nextAccountNumber = 1000;
private static List<BankAccount> _allAccounts = new List<BankAccount>();
// Static property
public static decimal InterestRate
{
get => _interestRate;
set
{
if (value < 0 || value > 0.1m)
throw new ArgumentException("Invalid interest rate");
_interestRate = value;
}
}
// Static read-only property
public static string BankName { get; } = "National Bank";
// Constructor (instance)
public BankAccount(decimal initialDeposit)
{
AccountNumber = $"ACC{_nextAccountNumber++}";
Balance = initialDeposit;
_allAccounts.Add(this); // Add to static list
}
// Instance method
public void Deposit(decimal amount)
{
Balance += amount;
Console.WriteLine($"{AccountNumber}: Deposited {amount:C}, New balance: {Balance:C}");
}
// Instance method using static member
public void AddInterest()
{
decimal interest = Balance * _interestRate;
Balance += interest;
Console.WriteLine($"{AccountNumber}: Interest added: {interest:C}");
}
// Static method
public static decimal GetTotalBalance()
{
decimal total = 0;
foreach (var account in _allAccounts)
{
total += account.Balance;
}
return total;
}
// Static method to find account
public static BankAccount FindAccount(string accountNumber)
{
return _allAccounts.FirstOrDefault(a => a.AccountNumber == accountNumber);
}
// Static constructor (called once before any instances)
static BankAccount()
{
Console.WriteLine($"Bank {BankName} initialized with rate: {InterestRate:P}");
// Load configuration, initialize static data, etc.
}
}

// Usage
Console.WriteLine($"Bank: {BankAccount.BankName}");
Console.WriteLine($"Interest Rate: {BankAccount.InterestRate:P}");

// Create instances
var account1 = new BankAccount(1000);
var account2 = new BankAccount(500);
var account3 = new BankAccount(2000);

// Instance operations
account1.Deposit(250);
account2.Deposit(100);
account1.AddInterest();

// Static operations
decimal total = BankAccount.GetTotalBalance();
Console.WriteLine($"Total bank balance: {total:C}");

var found = BankAccount.FindAccount("ACC1002");
Console.WriteLine($"Found account: {found?.AccountNumber}");

// Static vs Instance - Key Differences
// Static: BankAccount.InterestRate (shared)
// Instance: account1.Balance (unique per object)

When to use Static vs Instance:

public class Utility
{
// USE STATIC when:
// 1. No state needed (pure functions)
public static int Add(int a, int b) => a + b;
// 2. Shared data across instances
private static int _instanceCount = 0;
// 3. Helper/utility methods
public static string FormatDate(DateTime date) => date.ToString("yyyy-MM-dd");
// 4. Singleton pattern
private static Utility _instance;
public static Utility Instance => _instance ??= new Utility();
}

public class ShoppingCart
{
// USE INSTANCE when:
// 1. Each object has its own state
public List<string> Items { get; set; } = new();
// 2. Operations modify specific instance data
public void AddItem(string item) => Items.Add(item);
// 3. Different objects behave differently
public decimal CalculateTotal() => Items.Count * 10;
}

17. Inheritance

// BASE CLASS (Parent)
public class Animal
{
// Fields and properties
public string Name { get; set; }
public int Age { get; set; }
// Constructor
public Animal(string name)
{
Name = name;
Age = 0;
Console.WriteLine($"Animal constructor: {Name}");
}
// Virtual method - can be overridden
public virtual void MakeSound()
{
Console.WriteLine($"{Name} makes a sound");
}
// Regular method - cannot be overridden
public void Eat()
{
Console.WriteLine($"{Name} is eating");
}
// Sealed method - cannot be overridden in derived classes
public virtual void Sleep()
{
Console.WriteLine($"{Name} is sleeping");
}
}

// DERIVED CLASS (Child)
public class Dog : Animal // ':' means inherits from
{
public string Breed { get; set; }
// Constructor calling base constructor
public Dog(string name, string breed) : base(name) // Call Animal constructor
{
Breed = breed;
Console.WriteLine($"Dog constructor: {name} is a {breed}");
}
// Override virtual method
public override void MakeSound()
{
base.MakeSound(); // Call base class method (optional)
Console.WriteLine($"{Name} barks: Woof! Woof!");
}
// New method specific to Dog
public void Fetch()
{
Console.WriteLine($"{Name} is fetching the ball!");
}
// Sealed override - cannot be overridden further
public sealed override void Sleep()
{
Console.WriteLine($"{Name} curls up and sleeps");
}
}

// Further inheritance
public class Puppy : Dog
{
public Puppy(string name, string breed) : base(name, breed)
{
}
public override void MakeSound()
{
Console.WriteLine($"{Name} yips: Yip! Yip!");
}
// Cannot override Sleep() because it's sealed in Dog
// public override void Sleep() { } // ERROR!
}

// MULTILEVEL INHERITANCE
public class Vehicle
{
public string Brand { get; set; }
public void Start() => Console.WriteLine("Vehicle started");
}

public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public void Honk() => Console.WriteLine("Beep beep!");
}

public class SportsCar : Car
{
public bool HasTurbo { get; set; }
public void Accelerate() => Console.WriteLine("Vroom vroom!");
}

// Usage
class Program
{
static void Main()
{
// Create derived object
Dog myDog = new Dog("Rex", "German Shepherd");
myDog.Age = 3;
myDog.Eat(); // Inherited from Animal
myDog.MakeSound(); // Overridden in Dog
myDog.Fetch(); // Dog-specific method
// SportsCar example
SportsCar ferrari = new SportsCar();
ferrari.Brand = "Ferrari"; // From Vehicle
ferrari.NumberOfDoors = 2; // From Car
ferrari.HasTurbo = true; // From SportsCar
ferrari.Start(); // From Vehicle
ferrari.Honk(); // From Car
ferrari.Accelerate(); // From SportsCar
// POLYMORPHISM - Base class reference to derived object
Animal animal = new Dog("Buddy", "Labrador");
animal.MakeSound(); // Calls Dog's version (polymorphism)
// animal.Fetch(); // ERROR! Animal doesn't have Fetch()
// Type checking
if (animal is Dog)
{
Dog dog = (Dog)animal;
dog.Fetch(); // Now we can call Fetch()
}
// Better way with 'as'
Dog dog2 = animal as Dog;
if (dog2 != null)
{
dog2.Fetch();
}
}
}

18. Polymorphism (Many Forms)

// COMPILE-TIME POLYMORPHISM (Method Overloading)
public class MathOperations
{
// Same method name, different parameters
public int Add(int a, int b) => a + b;
public int Add(int a, int b, int c) => a + b + c;
public double Add(double a, double b) => a + b;
public string Add(string a, string b) => a + b;
}

// RUNTIME POLYMORPHISM (Method Overriding)
public abstract class Shape
{
public abstract double CalculateArea();
public virtual string GetName() => "Shape";
}

public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius) => Radius = radius;
public override double CalculateArea() => Math.PI * Radius * Radius;
public override string GetName() => "Circle";
}

public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public override double CalculateArea() => Width * Height;
public override string GetName() => "Rectangle";
}

public class Triangle : Shape
{
public double Base { get; set; }
public double Height { get; set; }
public Triangle(double @base, double height)
{
Base = @base;
Height = height;
}
public override double CalculateArea() => (Base * Height) / 2;
}

// Polymorphism in action
class ShapeCalculator
{
// This method works with ANY Shape (polymorphism)
public void PrintArea(Shape shape)
{
Console.WriteLine($"The {shape.GetName()} has area: {shape.CalculateArea():F2}");
}
// Process any collection of shapes
public double TotalArea(List<Shape> shapes)
{
double total = 0;
foreach (var shape in shapes)
{
total += shape.CalculateArea(); // Calls appropriate method
}
return total;
}
}

// Usage
var calculator = new ShapeCalculator();

var shapes = new List<Shape>
{
new Circle(5),
new Rectangle(4, 6),
new Triangle(3, 4),
new Circle(3),
new Rectangle(2, 8)
};

foreach (var shape in shapes)
{
calculator.PrintArea(shape);
}

double total = calculator.TotalArea(shapes);
Console.WriteLine($"Total area: {total:F2}");

// Output:
// The Circle has area: 78.54
// The Rectangle has area: 24.00
// The Triangle has area: 6.00
// etc.

19. Abstract Classes and Methods

// ABSTRACT CLASS - Cannot be instantiated, can contain abstract and concrete members
public abstract class DatabaseConnection
{
// Abstract property - must be implemented by derived classes
public abstract string ConnectionString { get; set; }
// Abstract method - no implementation, must be overridden
public abstract void Connect();
public abstract void Disconnect();
public abstract void ExecuteQuery(string sql);
// Concrete method - can be used as-is or overridden
public virtual void Log(string message)
{
Console.WriteLine($"[{DateTime.Now}] {message}");
}
// Concrete method with implementation
public bool IsConnected { get; protected set; }
// Constructor (allowed in abstract classes)
protected DatabaseConnection()
{
Log("Database connection created");
}
}

// Concrete implementation
public class SqlConnection : DatabaseConnection
{
private string _connectionString;
public override string ConnectionString
{
get => _connectionString;
set => _connectionString = value;
}
public override void Connect()
{
Log($"Connecting to SQL Server: {ConnectionString}");
// Simulate connection logic
IsConnected = true;
Log("Connected successfully");
}
public override void Disconnect()
{
Log("Disconnecting from SQL Server");
IsConnected = false;
Log("Disconnected");
}
public override void ExecuteQuery(string sql)
{
if (!IsConnected)
throw new InvalidOperationException("Not connected to database");
Log($"Executing SQL: {sql}");
// Execute query logic
Log("Query executed");
}
}

public class MongoConnection : DatabaseConnection
{
public override string ConnectionString { get; set; }
public override void Connect()
{
Console.WriteLine($"Connecting to MongoDB: {ConnectionString}");
IsConnected = true;
}
public override void Disconnect()
{
Console.WriteLine("Disconnecting from MongoDB");
IsConnected = false;
}
public override void ExecuteQuery(string sql)
{
Console.WriteLine($"MongoDB doesn't use SQL! Converting: {sql}");
// MongoDB specific logic
}
}

// Abstract class with factory pattern
public abstract class AnimalFactory
{
public abstract Animal CreateAnimal(string name);
// Concrete method using abstract method
public Animal CreateAndMakeSound(string name)
{
var animal = CreateAnimal(name);
animal.MakeSound();
return animal;
}
}

public class DogFactory : AnimalFactory
{
public override Animal CreateAnimal(string name)
{
return new Dog(name, "Mixed");
}
}

// Usage
DatabaseConnection db = new SqlConnection();
db.ConnectionString = "Server=localhost;Database=MyDb";
db.Connect();
db.ExecuteQuery("SELECT * FROM Users");
db.Disconnect();

// Can't do: DatabaseConnection db2 = new DatabaseConnection(); // ERROR! Abstract

// Using factory
var factory = new DogFactory();
var dog = factory.CreateAndMakeSound("Rex");

Abstract Class vs Interface:

// WHEN TO USE ABSTRACT CLASS:
// - Share code among related classes
// - Provide default implementation
// - Have fields/state
// - Have constructors
// - Versioning (can add methods without breaking code)

// WHEN TO USE INTERFACE:
// - Multiple inheritance needed
// - Completely unrelated classes
// - Define contract only (no implementation)
// - Polymorphism across hierarchies

20. Interfaces

// INTERFACE - Contract that classes must follow
public interface IAnimal
{
// Properties (no implementation)
string Name { get; set; }
int Age { get; }
// Methods (no implementation)
void MakeSound();
void Eat();
// Events
event EventHandler OnHungry;
}

// Interface with default implementation (C# 8+)
public interface IMovable
{
void Move();
// Default implementation (C# 8+)
void Stop()
{
Console.WriteLine("Stopping...");
}
}

// Multiple interface inheritance
public interface IFlyable
{
void Fly();
int MaxAltitude { get; }
}

public interface ISwimmable
{
void Swim();
int MaxDepth { get; }
}

// Class implementing multiple interfaces
public class Duck : IAnimal, IFlyable, ISwimmable
{
public string Name { get; set; }
public int Age => 2;
public int MaxAltitude => 1000;
public int MaxDepth => 10;
public event EventHandler OnHungry;
public void MakeSound()
{
Console.WriteLine($"{Name} says: Quack! Quack!");
}
public void Eat()
{
Console.WriteLine($"{Name} is eating bread");
OnHungry?.Invoke(this, EventArgs.Empty);
}
public void Fly()
{
Console.WriteLine($"{Name} is flying at {MaxAltitude} feet");
}
public void Swim()
{
Console.WriteLine($"{Name} is swimming at {MaxDepth} meters");
}
// IMovable.Stop() already has default implementation, but we can override
public void Stop()
{
Console.WriteLine($"{Name} stops moving");
}
}

// Explicit interface implementation (when methods have same name)
public interface IPrintable
{
void Print();
}

public interface IScannable
{
void Print(); // Same method name!
}

public class MultiFunctionPrinter : IPrintable, IScannable
{
// Explicit implementation
void IPrintable.Print()
{
Console.WriteLine("Printing document...");
}
void IScannable.Print()
{
Console.WriteLine("Scanning document...");
}
// Public method for common use
public void Print()
{
Console.WriteLine("Default print behavior");
}
}

// Interface for dependency injection (very common)
public interface IRepository<T>
{
Task<T> GetByIdAsync(int id);
Task<IEnumerable<T>> GetAllAsync();
Task AddAsync(T entity);
Task UpdateAsync(T entity);
Task DeleteAsync(int id);
}

public class UserRepository : IRepository<User>
{
public async Task<User> GetByIdAsync(int id)
{
// Database logic
return await Task.FromResult(new User());
}
// Implement other methods...
}

// Interface segregation (I - Interface Segregation Principle)
// BAD: Fat interface
public interface IWorker
{
void Work();
void Eat();
void Sleep();
void AttendMeeting();
void WriteCode();
void Test();
}

// GOOD: Segregated interfaces
public interface IWorkable
{
void Work();
}

public interface IEatable
{
void Eat();
}

public interface ISleepable
{
void Sleep();
}

public interface IDeveloper : IWorkable, IEatable, ISleepable
{
void WriteCode();
void Test();
}

// Usage
Duck duck = new Duck { Name = "Donald" };
duck.MakeSound();
duck.Fly();
duck.Swim();

// Polymorphism with interfaces
List<IFlyable> flyingThings = new List<IFlyable>();
flyingThings.Add(new Duck());
flyingThings.Add(new Airplane());
// flyingThings.Add(new Fish()); // ERROR! Fish doesn't implement IFlyable

foreach (var flyer in flyingThings)
{
flyer.Fly();
}

// Repository usage
IRepository<User> userRepo = new UserRepository();
var user = await userRepo.GetByIdAsync(1);

21. Encapsulation (Getters/Setters, Properties)

public class BankAccount
{
// PRIVATE FIELDS - Hidden from outside
private string _accountNumber;
private decimal _balance;
private string _pin;
private List<string> _transactionHistory = new List<string>();
// PUBLIC PROPERTIES - Controlled access
public string AccountNumber
{
get => _accountNumber;
private set => _accountNumber = value; // Only set internally
}
// Read-only property
public decimal Balance => _balance;
// Property with validation
public string Pin
{
set
{
if (string.IsNullOrEmpty(value) || value.Length != 4)
throw new ArgumentException("PIN must be 4 digits");
if (!value.All(char.IsDigit))
throw new ArgumentException("PIN must contain only digits");
_pin = value;
}
}
// Property with side effects
public int TransactionCount
{
get => _transactionHistory.Count;
}
// Constructor
public BankAccount(string accountNumber, string pin, decimal initialDeposit)
{
AccountNumber = accountNumber;
Pin = pin; // Uses setter logic
_balance = initialDeposit;
AddTransaction("Account opened", initialDeposit);
}
// PUBLIC METHODS - Exposed functionality
public bool Withdraw(decimal amount, string pin)
{
// Validate PIN
if (pin != _pin)
{
AddTransaction("Failed withdrawal attempt - wrong PIN", -amount);
return false;
}
// Validate amount
if (amount <= 0)
{
AddTransaction("Failed withdrawal - invalid amount", amount);
return false;
}
// Check balance
if (amount > _balance)
{
AddTransaction($"Failed withdrawal - insufficient funds", -amount);
return false;
}
// Perform withdrawal
_balance -= amount;
AddTransaction($"Withdrawal", -amount);
return true;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Deposit amount must be positive");
_balance += amount;
AddTransaction("Deposit", amount);
}
public string[] GetTransactionHistory(string pin)
{
if (pin != _pin)
return new string[] { "Invalid PIN" };
return _transactionHistory.ToArray();
}
// PRIVATE METHODS - Internal implementation
private void AddTransaction(string description, decimal amount)
{
string transaction = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {description}: {amount:C}";
_transactionHistory.Add(transaction);
}
private bool ValidatePin(string pin) => pin == _pin;
}

// Usage
class Program
{
static void Main()
{
var account = new BankAccount("12345678", "1234", 1000);
// Can read public properties
Console.WriteLine($"Account: {account.AccountNumber}");
Console.WriteLine($"Balance: {account.Balance:C}");
// Can't access private fields directly
// account._balance = 5000; // ERROR! Private
// account._pin = "0000"; // ERROR! Private
// Use public methods
account.Deposit(500);
account.Withdraw(200, "1234");
bool success = account.Withdraw(5000, "1234"); // Insufficient funds
Console.WriteLine($"Withdrawal success: {success}");
// Get transaction history
string[] history = account.GetTransactionHistory("1234");
foreach (var transaction in history)
{
Console.WriteLine(transaction);
}
// Encapsulation protects data integrity
// account.Balance = 9999; // ERROR! No public setter
}
}

22. Method Parameters (ref, out, in, params)

public class ParameterDemo
{
// 1. VALUE PARAMETERS (default) - Pass by value (copy)
public void PassByValue(int x)
{
x = 100; // Changes only the local copy
Console.WriteLine($"Inside method: x = {x}");
}
// 2. REF PARAMETERS - Pass by reference (original modified)
public void PassByReference(ref int x)
{
x = 100; // Changes the original variable
Console.WriteLine($"Inside method: x = {x}");
}
// 3. OUT PARAMETERS - Multiple return values
public bool Divide(int numerator, int denominator, out int quotient, out int remainder)
{
if (denominator == 0)
{
quotient = 0;
remainder = 0;
return false;
}
quotient = numerator / denominator;
remainder = numerator % denominator;
return true;
}
// 4. IN PARAMETERS - Read-only reference (performance for large structs)
public void PrintLargeStruct(in LargeStruct data)
{
// Cannot modify data
Console.WriteLine($"Value: {data.Value}");
}
// 5. PARAMS - Variable number of arguments
public int Sum(params int[] numbers)
{
int total = 0;
foreach (int num in numbers)
{
total += num;
}
return total;
}
// 6. OPTIONAL PARAMETERS - Default values
public void Greet(string name, string greeting = "Hello")
{
Console.WriteLine($"{greeting}, {name}!");
}
// 7. NAMED ARGUMENTS - Call with parameter names
public void CreateUser(string name, int age, string email, bool isActive = true)
{
Console.WriteLine($"User: {name}, Age: {age}, Email: {email}, Active: {isActive}");
}
// 8. Ref locals and returns (C# 7.0+)
private int[] _numbers = { 1, 2, 3, 4, 5 };
public ref int FindNumber(int target)
{
for (int i = 0; i < _numbers.Length; i++)
{
if (_numbers[i] == target)
return ref _numbers[i]; // Return reference to array element
}
throw new Exception("Number not found");
}
}

public struct LargeStruct
{
public long Value1, Value2, Value3, Value4, Value5;
public int Value;
}

// Usage
class Program
{
static void Main()
{
var demo = new ParameterDemo();
// Value parameter
int a = 10;
demo.PassByValue(a);
Console.WriteLine($"After PassByValue: a = {a}"); // Still 10
// Ref parameter
int b = 10;
demo.PassByReference(ref b);
Console.WriteLine($"After PassByReference: b = {b}"); // Now 100
// Out parameters
int quotient, remainder;
bool success = demo.Divide(10, 3, out quotient, out remainder);
Console.WriteLine($"10 / 3 = {quotient} remainder {remainder}");
// Or with inline declaration (C# 7+)
demo.Divide(10, 3, out int q, out int r);
// Params
int sum1 = demo.Sum(1, 2, 3, 4, 5);
int sum2 = demo.Sum(10, 20);
int sum3 = demo.Sum(); // Empty array
int[] numbers = { 1, 2, 3 };
int sum4 = demo.Sum(numbers);
// Optional parameters
demo.Greet("John"); // Uses default "Hello"
demo.Greet("Jane", "Hi"); // Overrides default
// Named arguments (order doesn't matter)
demo.CreateUser(age: 30, email: "john@example.com", name: "John");
// Ref locals and returns
ref int numberRef = ref demo.FindNumber(3);
numberRef = 100; // Changes the original array element
Console.WriteLine(demo.FindNumber(3)); // Now 100
}
}

23. Partial Classes and Methods

// File: Person.cs (Part 1)
namespace PartialExample
{
// Partial class - definition split across multiple files
public partial class Person
{
// Fields and properties in one file
private string _firstName;
private string _lastName;
private int _age;
public Person(string firstName, string lastName)
{
_firstName = firstName;
_lastName = lastName;
}
// Partial method declaration (no body)
partial void OnAgeChanged();
public string FirstName
{
get => _firstName;
set => _firstName = value;
}
public int Age
{
get => _age;
set
{
_age = value;
OnAgeChanged(); // Call partial method
}
}
}
}

// File: Person.Methods.cs (Part 2)
namespace PartialExample
{
public partial class Person
{
// Methods in another file
public string GetFullName()
{
return $"{_firstName} {_lastName}";
}
public void DisplayInfo()
{
Console.WriteLine($"Name: {GetFullName()}, Age: {_age}");
}
// Partial method implementation (optional)
partial void OnAgeChanged()
{
Console.WriteLine($"Age changed to {_age}");
// Validate age, trigger events, etc.
}
}
}

// File: Person.Validation.cs (Part 3)
namespace PartialExample
{
public partial class Person
{
// Validation logic in separate file
public bool IsValid()
{
return !string.IsNullOrEmpty(_firstName) &&
!string.IsNullOrEmpty(_lastName) &&
_age >= 0 && _age <= 150;
}
}
}

// Common use case: Generated code + custom code
// File: AutoGenerated.cs (generated by tool)
public partial class DataContext
{
// Auto-generated code from database
public string ConnectionString { get; set; }
public void Connect()
{
// Auto-generated connection logic
}
partial void OnConnecting(); // Hook for custom code
partial void OnConnected();
}

// File: CustomCode.cs (developer written)
public partial class DataContext
{
// Custom code
partial void OnConnecting()
{
Console.WriteLine("About to connect to database...");
}
partial void OnConnected()
{
Console.WriteLine("Connected successfully!");
// Log connection, initialize caching, etc.
}
}

Module 1 Practice Exercises

Exercise 1: Create a Banking System

// Requirements:
// 1. Create BankAccount class with encapsulation
// 2. Properties: AccountNumber, Balance, OwnerName
// 3. Methods: Deposit, Withdraw, Transfer
// 4. Use validation in property setters
// 5. Track transaction history

// Solution structure:
public class BankAccount
{
private decimal _balance;
private List<Transaction> _transactions = new();
public string AccountNumber { get; }
public string OwnerName { get; set; }
public decimal Balance => _balance;
public BankAccount(string ownerName, decimal initialDeposit)
{
OwnerName = ownerName;
AccountNumber = GenerateAccountNumber();
Deposit(initialDeposit);
}
public void Deposit(decimal amount) { /* implementation */ }
public bool Withdraw(decimal amount) { /* implementation */ }
public bool Transfer(BankAccount to, decimal amount) { /* implementation */ }
public Transaction[] GetTransactionHistory() { /* implementation */ }
}

Exercise 2: Inheritance Hierarchy

// Create hierarchy:
// Shape (abstract)
// ├── Circle
// ├── Rectangle
// └── Triangle
// Each shape calculates area differently
// Demonstrate polymorphism with List<Shape>

// Solution:
public abstract class Shape
{
public abstract double CalculateArea();
public abstract double CalculatePerimeter();
}

public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius) => Radius = radius;
public override double CalculateArea() => Math.PI * Radius * Radius;
public override double CalculatePerimeter() => 2 * Math.PI * Radius;
}
// Implement Rectangle and Triangle similarly...

Exercise 3: Interface Implementation

// Create interfaces:
// IComparable<T> for sorting
// IPrintable for displaying
// ISerializable for saving/loading

public class Student : IComparable<Student>, IPrintable, ISerializable
{
public string Name { get; set; }
public int Grade { get; set; }
public int CompareTo(Student other)
{
return Grade.CompareTo(other.Grade);
}
public void Print() { }
public void Save(string filepath) { }
public void Load(string filepath) { }
}

// Then sort students by grade
List<Student> students = new List<Student>();
students.Sort(); // Uses IComparable

Module 1 Summary

You've learned:

  1. ✅ Classes and Objects (blueprints and instances)
  2. ✅ Fields and Properties (data encapsulation)
  3. ✅ Methods (behavior, overloading, parameters)
  4. ✅ Constructors/Destructors (object lifecycle)
  5. ✅ Access Modifiers (public, private, protected, etc.)
  6. ✅ Static vs Instance (shared vs per-object)
  7. ✅ Inheritance (code reuse, IS-A relationship)
  8. ✅ Polymorphism (many forms, virtual/override)
  9. ✅ Abstract Classes (incomplete blueprints)
  10. ✅ Interfaces (contracts, multiple inheritance)
  11. ✅ Encapsulation (data hiding, validation)
  12. ✅ Partial Classes (split definitions)