C# Classes and Objects
This chapter will detail classes and objects in C#, the foundation of object-oriented programming, including class definition, object creation, constructors, destructors, and related concepts.
Object-Oriented Programming Basics
What is a Class?
A class is a blueprint or template for creating objects. It defines the properties (attributes) and behaviors (methods) that objects of that type will have.
What is an Object?
An object is an instance of a class. It's a concrete entity that has actual values for its properties and can perform actions through its methods.
Class Definition
Basic Class Structure
public class Person
{
// Fields (attributes)
public string Name;
public int Age;
public string Email;
// Constructor
public Person(string name, int age, string email)
{
Name = name;
Age = age;
Email = email;
}
// Methods (behaviors)
public void Introduce()
{
Console.WriteLine($"Hi, I'm {Name}, {Age} years old.");
}
public void SendEmail(string message)
{
Console.WriteLine($"Sending email to {Email}: {message}");
}
}Access Modifiers
public class AccessExample
{
// Public: accessible from anywhere
public string PublicField = "Public";
// Private: accessible only within this class
private string PrivateField = "Private";
// Protected: accessible within this class and derived classes
protected string ProtectedField = "Protected";
// Internal: accessible within the same assembly
internal string InternalField = "Internal";
// Protected Internal: accessible within same assembly and derived classes
protected internal string ProtectedInternalField = "Protected Internal";
// Property to control access
public string ReadOnlyProperty { get; private set; }
// Method with different access levels
public void PublicMethod() { }
private void PrivateMethod() { }
protected void ProtectedMethod() { }
internal void InternalMethod() { }
}Constructors
Default Constructor
public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Major { get; set; }
// Default constructor
public Student()
{
Name = "Unknown";
Age = 0;
Major = "Undeclared";
}
public void DisplayInfo()
{
Console.WriteLine($"Student: {Name}, Age: {Age}, Major: {Major}");
}
}Parameterized Constructors
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
// Parameterized constructor
public Product(int id, string name, decimal price, string category)
{
Id = id;
Name = name;
Price = price;
Category = category;
}
// Constructor overloading
public Product(int id, string name, decimal price)
: this(id, name, price, "General")
{
}
public void DisplayInfo()
{
Console.WriteLine($"Product: {Name} (${Price}) - {Category}");
}
}Constructor Chaining
public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
public string Department { get; set; }
public decimal Salary { get; set; }
// Main constructor
public Employee(string name, int age, string department, decimal salary)
{
Name = name;
Age = age;
Department = department;
Salary = salary;
Console.WriteLine("Employee created with all parameters");
}
// Constructor chaining
public Employee(string name, int age, string department)
: this(name, age, department, 50000m)
{
Console.WriteLine("Employee created with default salary");
}
public Employee(string name, int age)
: this(name, age, "General", 40000m)
{
Console.WriteLine("Employee created with default department and salary");
}
}Properties
Auto-Implemented Properties
public class Person
{
// Auto-implemented properties
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
// Computed property
public string FullName
{
get { return $"{FirstName} {LastName}"; }
}
// Read-only property
public bool IsAdult
{
get { return Age >= 18; }
}
}Properties with Validation
public class BankAccount
{
private decimal _balance;
public decimal Balance
{
get { return _balance; }
set
{
if (value < 0)
throw new ArgumentException("Balance cannot be negative");
_balance = value;
}
}
private string _accountNumber;
public string AccountNumber
{
get { return _accountNumber; }
private set { _accountNumber = value; }
}
public BankAccount(string accountNumber, decimal initialBalance)
{
AccountNumber = accountNumber;
Balance = initialBalance;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Deposit amount must be positive");
Balance += amount;
}
public bool Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Withdrawal amount must be positive");
if (Balance >= amount)
{
Balance -= amount;
return true;
}
return false;
}
}Static Members
Static Fields and Properties
public class Counter
{
// Static field - shared by all instances
private static int _instanceCount = 0;
// Static property
public static int InstanceCount
{
get { return _instanceCount; }
}
// Instance property
public int Id { get; private set; }
public Counter()
{
_instanceCount++;
Id = _instanceCount;
}
// Static method
public static void ResetCounter()
{
_instanceCount = 0;
}
public void DisplayInfo()
{
Console.WriteLine($"Instance {Id} of {InstanceCount} total instances");
}
}Static Classes
public static class MathHelper
{
public const double PI = 3.14159;
// Static methods - no instance needed
public static double CalculateCircleArea(double radius)
{
return PI * radius * radius;
}
public static int Max(int a, int b)
{
return a > b ? a : b;
}
public static bool IsEven(int number)
{
return number % 2 == 0;
}
// Static constructor
static MathHelper()
{
Console.WriteLine("MathHelper class initialized");
}
}
// Usage
double area = MathHelper.CalculateCircleArea(5.0);
int max = MathHelper.Max(10, 20);
bool isEven = MathHelper.IsEven(4);Methods in Classes
Instance Methods
public class Calculator
{
private double _result;
public double Add(double value)
{
_result += value;
return _result;
}
public double Subtract(double value)
{
_result -= value;
return _result;
}
public double Multiply(double value)
{
_result *= value;
return _result;
}
public double Divide(double value)
{
if (value == 0)
throw new DivideByZeroException("Cannot divide by zero");
_result /= value;
return _result;
}
public double GetResult()
{
return _result;
}
public void Clear()
{
_result = 0;
}
}Method Overloading
public class Logger
{
public void Log(string message)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
}
public void Log(string message, LogLevel level)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}");
}
public void Log(string message, LogLevel level, string category)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] [{category}] {message}");
}
public void Log(Exception exception)
{
Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [ERROR] {exception.Message}");
Console.WriteLine($"Stack Trace: {exception.StackTrace}");
}
}
public enum LogLevel
{
INFO, WARNING, ERROR, DEBUG
}Object Creation and Usage
Creating Objects
// Create objects using constructors
Person person1 = new Person(); // Default constructor
Person person2 = new Person("Alice", 25, "alice@example.com"); // Parameterized constructor
Product product1 = new Product(1, "Laptop", 999.99m, "Electronics");
Product product2 = new Product(2, "Mouse", 29.99m); // Uses constructor chaining
// Create objects using object initializer
Person person3 = new Person
{
Name = "Bob",
Age = 30,
Email = "bob@example.com"
};
Product product3 = new Product
{
Id = 3,
Name = "Keyboard",
Price = 79.99m,
Category = "Electronics"
};Using Objects
// Use object methods and properties
person2.Introduce();
person2.SendEmail("Welcome to our team!");
product1.DisplayInfo();
product2.DisplayInfo();
// Access properties
Console.WriteLine($"Person name: {person3.Name}");
Console.WriteLine($"Person is adult: {person3.IsAdult}");
// Modify properties
person3.Age = 31;
Console.WriteLine($"Updated age: {person3.Age}");Inheritance Basics
Base and Derived Classes
// Base class
public class Vehicle
{
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public Vehicle(string brand, string model, int year)
{
Brand = brand;
Model = model;
Year = year;
}
public virtual void Start()
{
Console.WriteLine($"{Brand} {Model} starting...");
}
public virtual void Stop()
{
Console.WriteLine($"{Brand} {Model} stopping...");
}
}
// Derived class
public class Car : Vehicle
{
public int NumberOfDoors { get; set; }
public string FuelType { get; set; }
public Car(string brand, string model, int year, int doors, string fuel)
: base(brand, model, year)
{
NumberOfDoors = doors;
FuelType = fuel;
}
public override void Start()
{
Console.WriteLine($"Car {Brand} {Model} ({FuelType}) starting with key...");
base.Start();
}
public void OpenTrunk()
{
Console.WriteLine("Opening car trunk...");
}
}this Keyword
this Keyword Usage
public class Student
{
public string Name { get; private set; }
public int Age { get; private set; }
public double GPA { get; private set; }
public Student(string name, int age, double gpa)
{
// Distinguish between parameter and field
this.Name = name;
this.Age = age;
this.GPA = gpa;
}
// Return current object
public Student Clone()
{
return new Student(this.Name, this.Age, this.GPA);
}
// Pass current object to another method
public void PrintInfo()
{
Printer.PrintStudentInfo(this);
}
}
public static class Printer
{
public static void PrintStudentInfo(Student student)
{
Console.WriteLine($"Student: {student.Name}, Age: {student.Age}, GPA: {student.GPA}");
}
}Object Lifecycle
Constructor Execution Order
public class LifecycleDemo
{
public static int InstanceCount { get; private set; }
public LifecycleDemo()
{
InstanceCount++;
Console.WriteLine("Instance constructor called");
}
static LifecycleDemo()
{
Console.WriteLine("Static constructor called");
}
~LifecycleDemo()
{
InstanceCount--;
Console.WriteLine("Destructor called");
}
}Practical Examples
Bank Account System
public class BankAccount
{
private static decimal _totalDeposits = 0;
private static int _accountCount = 0;
public string AccountNumber { get; }
public string OwnerName { get; }
public decimal Balance { get; private set; }
public DateTime CreatedDate { get; }
public static decimal TotalDeposits => _totalDeposits;
public static int AccountCount => _accountCount;
public BankAccount(string accountNumber, string ownerName, decimal initialDeposit)
{
AccountNumber = accountNumber;
OwnerName = ownerName;
Balance = initialDeposit;
CreatedDate = DateTime.Now;
_accountCount++;
_totalDeposits += initialDeposit;
}
public void Deposit(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Deposit amount must be positive");
Balance += amount;
_totalDeposits += amount;
Console.WriteLine($"Deposited ${amount} to account {AccountNumber}");
}
public bool Withdraw(decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Withdrawal amount must be positive");
if (Balance < amount)
{
Console.WriteLine($"Insufficient funds in account {AccountNumber}");
return false;
}
Balance -= amount;
Console.WriteLine($"Withdrew ${amount} from account {AccountNumber}");
return true;
}
public void DisplayBalance()
{
Console.WriteLine($"Account {AccountNumber} Balance: ${Balance:N2}");
}
public static void DisplayBankStatistics()
{
Console.WriteLine($"Total Accounts: {AccountCount}");
Console.WriteLine($"Total Deposits: ${TotalDeposits:N2}");
}
}Product Catalog
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
public int StockQuantity { get; set; }
public bool IsInStock => StockQuantity > 0;
public Product(int id, string name, decimal price, string category, int stock)
{
Id = id;
Name = name;
Price = price;
Category = category;
StockQuantity = stock;
}
public void ApplyDiscount(decimal percentage)
{
if (percentage < 0 || percentage > 100)
throw new ArgumentException("Invalid discount percentage");
Price *= (1 - percentage / 100);
}
public bool Sell(int quantity)
{
if (quantity <= 0)
throw new ArgumentException("Quantity must be positive");
if (StockQuantity < quantity)
{
Console.WriteLine($"Insufficient stock for {Name}. Available: {StockQuantity}, Requested: {quantity}");
return false;
}
StockQuantity -= quantity;
Console.WriteLine($"Sold {quantity} units of {Name}. Remaining stock: {StockQuantity}");
return true;
}
public void Restock(int quantity)
{
if (quantity <= 0)
throw new ArgumentException("Restock quantity must be positive");
StockQuantity += quantity;
Console.WriteLine($"Restocked {quantity} units of {Name}. New stock: {StockQuantity}");
}
public void DisplayInfo()
{
Console.WriteLine($"Product: {Name} (ID: {Id})");
Console.WriteLine($"Category: {Category}");
Console.WriteLine($"Price: ${Price:N2}");
Console.WriteLine($"Stock: {StockQuantity} units");
Console.WriteLine($"Status: {(IsInStock ? "In Stock" : "Out of Stock")}");
}
}
public class ProductCatalog
{
private List<Product> _products = new List<Product>();
public void AddProduct(Product product)
{
_products.Add(product);
}
public Product FindProduct(int id)
{
return _products.FirstOrDefault(p => p.Id == id);
}
public List<Product> GetProductsByCategory(string category)
{
return _products.Where(p => p.Category.Equals(category, StringComparison.OrdinalIgnoreCase)).ToList();
}
public List<Product> GetInStockProducts()
{
return _products.Where(p => p.IsInStock).ToList();
}
public void DisplayCatalog()
{
Console.WriteLine("=== Product Catalog ===");
foreach (var product in _products)
{
product.DisplayInfo();
Console.WriteLine();
}
}
public decimal CalculateTotalInventoryValue()
{
return _products.Sum(p => p.Price * p.StockQuantity);
}
}Best Practices
Class Design Principles
// Single Responsibility Principle
public class UserValidator
{
// Only validates user data
public bool IsValidEmail(string email) { /* validation logic */ }
public bool IsValidAge(int age) { /* validation logic */ }
}
public class UserRepository
{
// Only handles user data persistence
public void Save(User user) { /* save logic */ }
public User Load(int id) { /* load logic */ }
}Encapsulation
public class EncapsulatedExample
{
private int _age;
public int Age
{
get { return _age; }
set
{
if (value < 0 || value > 150)
throw new ArgumentException("Age must be between 0 and 150");
_age = value;
}
}
// Provide methods for business logic
public bool IsAdult() => Age >= 18;
public bool IsSenior() => Age >= 65;
}Constructor Guidelines
public class GoodConstructor
{
public string Name { get; }
public DateTime CreatedAt { get; }
// Initialize all required fields
public GoodConstructor(string name)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
CreatedAt = DateTime.Now;
}
// Provide factory methods for common scenarios
public static GoodConstructor CreateDefault()
{
return new GoodConstructor("Default User");
}
public static GoodConstructor CreateWithTimestamp(string name, DateTime timestamp)
{
return new GoodConstructor(name) { CreatedAt = timestamp };
}
}Summary
In this chapter, you learned:
- Class and object concepts
- Access modifiers and their usage
- Constructors: default, parameterized, chaining
- Properties: auto-implemented, with validation
- Static members: fields, properties, methods, classes
- Method overloading in classes
- this keyword usage
- Object lifecycle management
- Practical examples and best practices
Classes and objects are fundamental to object-oriented programming in C#. They provide the structure for organizing code into reusable, maintainable components. In the next chapter, we'll explore encapsulation and properties in more detail.