By the end of this session, you will be able to:
| Time | Segment | Type | Duration |
|---|---|---|---|
| 0:00 | Classes & objects | Theory + Demo | 25 min |
| 0:25 | Constructors & the this keyword | Theory + Demo | 20 min |
| 0:45 | Properties & access modifiers | Theory + Demo | 25 min |
| 1:10 | Break | — | 10 min |
| 1:20 | Static members | Theory + Demo | 15 min |
| 1:35 | Inheritance & the base keyword | Theory + Demo | 25 min |
| 2:00 | virtual & override | Theory + Demo | 20 min |
| 2:20 | Lab | Lab | 10 min |
new keywordBrand, YearStartEngine()public modifier on the class makes it accessible from other filespublic class Car
{
public string Brand;
public int Year;
public void StartEngine()
{
Console.WriteLine(
$"{Brand} engine started.");
}
}
new ClassName() allocates a fresh instance on the heapCar car1 = new Car();
car1.Brand = "Toyota";
car1.Year = 2022;
car1.StartEngine();
// "Toyota engine started."
// Object initializer — same result
Car car2 = new Car
{
Brand = "BMW",
Year = 2024
};
car2.StartEngine();
// "BMW engine started."
this Keywordthis refers to the current instance and chains constructors together.this(...)this as current instance referencepublic class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
// Constructor is called here
Person p = new Person("Alice", 30);
public class Person
{
public string Name;
public int Age;
public string Email;
// Delegates to the three-parameter constructor
public Person(string name, int age) : this(name, age, "")
{
}
public Person(string name, int age, string email)
{
Name = name;
Age = age;
Email = email;
}
}
this(...) calls another constructor in the same class — avoids duplicating initialisation logic across overloads.
this Keywordthis refers to the current instance of the classthis from a method enables method chaining — calling multiple methods in one expressionthis(...) used to chain constructors in the previous slidepublic class Counter
{
private int _count;
public void Increment() => _count++;
public Counter Reset()
{
_count = 0;
return this; // same object — enables chaining
}
}
var c = new Counter();
c.Increment();
c.Increment();
c.Reset().Increment(); // chain calls
| Modifier | Accessible from |
|---|---|
public | Anywhere |
private | Only inside the same class |
protected | Same class and derived classes |
internal | Same assembly (project) |
protected internal | Same assembly or derived classes |
private. Expose data through properties — that way you can add validation later without breaking any code that uses the class.
get and set accessorsprivate set restricts writes to inside the classpublic class Product
{
public string Name { get; set; }
public decimal Price { get; set; }
// Only settable from inside this class
public int Stock { get; private set; }
public void Restock(int units)
{
Stock += units;
}
}
public class Product
{
private decimal _price;
public decimal Price
{
get => _price;
set
{
if (value < 0)
throw new ArgumentException(
"Price cannot be negative.");
_price = value;
}
}
}
Use a backing field when you need validation, computed transformations, or side effects inside get or set.
get is read-only from outside the class=> expr computes the value on demand — no stored field neededpublic class Rectangle
{
public double Width { get; set; }
public double Height { get; set; }
// Computed — no backing field required
public double Area
=> Width * Height;
public double Perimeter
=> 2 * (Width + Height);
}
var r = new Rectangle
{ Width = 4, Height = 6 };
Console.WriteLine(r.Area); // 24
Console.WriteLine(r.Perimeter); // 20
thisConsole.WriteLine, Math.Sqrt, and string.Join are all static methods.
public class MathHelper
{
public static double Pi = 3.14159;
public static int Square(int n) => n * n;
public static double CircleArea(double r)
=> Pi * r * r;
}
// Called on the class, not on an instance
double pi = MathHelper.Pi;
int sq = MathHelper.Square(5); // 25
double a = MathHelper.CircleArea(3); // 28.27...
public class User
{
private static int _totalCreated = 0; // one copy — shared by all instances
public string Name { get; set; }
public User(string name)
{
Name = name;
_totalCreated++; // every constructor call increments it
}
public static int TotalCreated => _totalCreated;
}
var u1 = new User("Alice");
var u2 = new User("Bob");
Console.WriteLine(User.TotalCreated); // 2
Static fields are shared — every instance reads and writes the same memory location.
base KeywordDog IS-A Animal
: syntax declares a derived classprivate members of the base class are inherited automaticallypublic class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
public void Breathe()
{
Console.WriteLine(
$"{Name} is breathing.");
}
}
base Keywordbase(args) in a constructor calls the parent's constructorbase.MethodName() inside a method calls the parent's implementationpublic class Dog : Animal
{
public string Breed { get; set; }
public Dog(string name, string breed)
: base(name) // calls Animal(string name)
{
Breed = breed;
}
public void Bark()
=> Console.WriteLine($"{Name} says: Woof!");
}
Dog d = new Dog("Rex", "Labrador");
d.Breathe(); // inherited — "Rex is breathing."
d.Bark(); // "Rex says: Woof!"
virtual & overridevirtual to allow derived classes to replace it. Use override to supply the new implementation.virtual on a base-class method signals "derived classes may supply their own implementation"override in the derived class supplies that implementation — the compiler requires the keyword explicitly, preventing accidental hidingpublic class Shape
{
public virtual double Area() => 0;
}
public class Circle : Shape
{
public double Radius { get; set; }
public Circle(double r) => Radius = r;
public override double Area()
=> Math.PI * Radius * Radius;
}
public class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double w, double h)
{ Width = w; Height = h; }
public override double Area() => Width * Height;
}
Shape[] shapes =
{
new Circle(5),
new Rectangle(4, 6)
};
foreach (Shape s in shapes)
{
Console.WriteLine($"Area: {s.Area():F2}");
}
// Output:
// Area: 78.54 (Circle)
// Area: 24.00 (Rectangle)
The variable type is Shape, but the object's actual type decides which Area() runs.
sealed on a class prevents any class from inheriting itsealed override on a method stops further overriding down the hierarchystring and DateTime in the .NET base library are both sealed// No class may inherit from ImmutablePoint
public sealed class ImmutablePoint
{
public int X { get; }
public int Y { get; }
public ImmutablePoint(int x, int y)
{
X = x;
Y = y;
}
}
// Compile error:
// public class Special : ImmutablePoint { }
WorkItem with properties Id (int), Title (string), CreatedAt (DateTime), and a virtual string Summarize() methodTaskItem : WorkItem — add IsCompleted (bool) and DueDate (DateTime?). Override Summarize() to include the completion statusBugReport : WorkItem — add Severity (string). Override Summarize() to include the severity levelWorkItem[], and loop over the array printing each summaryprivate; expose them through properties so callers are shielded from internal changesbase(args) delegates to the parent constructorvirtual to allow overriding — at runtime the most-derived override always wins (polymorphism)Next session: Session 06 — OOP II: Polymorphism, Abstraction & Interfaces
is and asWorkItem hierarchy will be refactored into an abstract class next session. The assignment below is good preparation.
Build a BankAccount class hierarchy that exercises encapsulation, properties, inheritance, and method overriding.
BankAccount with a private decimal _balance field, a read-only Balance property, an Owner string, and Deposit / Withdraw methods that validate their argumentsvirtual string GetAccountType() method that returns "Standard"SavingsAccount : BankAccount — add InterestRate (decimal) and ApplyInterest(); override GetAccountType() to return "Savings"BankAccount and a SavingsAccount in a BankAccount[] and print GetAccountType() and Balance for eachBalance directly from outside the class should not compile — demonstrate this in a commentPremiumSavingsAccount : SavingsAccount that doubles the interest rate on each ApplyInterest() call and overrides GetAccountType() to return "Premium Savings".