Classes, properties, and interfaces
Auto-properties instead of hand-written getters, interfaces as contracts, and the difference between override and new.
After this lesson you can
- Write a class with auto-implemented properties and a constructor
- Choose between an interface and an abstract base class for a given design
- Call a method polymorphically through an interface reference
A property looks like a field from the outside and is really a pair of methods underneath.
public class Account
{
public string Owner { get; } // read-only after construction
public decimal Balance { get; private set; }
public Account(string owner, decimal balance = 0)
{
Owner = owner;
Balance = balance;
}
public void Deposit(decimal amount) => Balance += amount;
}
{ get; private set; } is an auto-property: the compiler generates the
backing field for you. Balance can be read from anywhere but only
changed from inside Account itself — encapsulation without writing a
private field and a public getter by hand.
Try it
public class Account{ public string Owner { get; } public decimal Balance { get; private set; } public Account(string owner, decimal balance = 0) { Owner = owner; Balance = balance; } public void Deposit(decimal amount) => Balance += amount;} public static class Solution{ public static string Describe() { var acc = new Account("Nino", 100); acc.Deposit(50); return $"{acc.Owner}: {acc.Balance}"; }}Interfaces
public interface INotifier
{
string Send(string message);
}
public class EmailNotifier : INotifier
{
public string Address { get; init; } = "";
public string Send(string message) => $"mail to {Address}: {message}";
}
public class SmsNotifier : INotifier
{
public string Number { get; init; } = "";
public string Send(string message) => $"sms to {Number}: {message}";
}
A C# class can implement any number of interfaces but inherit from only one base class — the same single-inheritance-for-classes, unlimited-for-interfaces split most object-oriented languages share. An interface can declare a default method body too (since C# 8), but its main job is still the contract: what a type promises to do. It cannot declare instance fields, so per-object state lives in the classes that implement it.
Polymorphism through the interface
List<INotifier> targets = new()
{
new EmailNotifier { Address = "ops@example.com" },
new SmsNotifier { Number = "555-0100" },
};
List<string> sent = targets.Select(t => t.Send("deploy finished")).ToList();
The loop — or here, Select — never needs to know which concrete type
each INotifier actually is. Which Send() runs is decided at runtime
by the object's real type, which is exactly what makes adding a
ChatNotifier : INotifier later free at every place that already only
ever depended on INotifier.
Common mistakes
public class Animal{ public virtual string Speak() => "..."; public string Name() => "animal";} public class Dog : Animal{ public override string Speak() => "woof"; public new string Name() => "dog";} public static class Solution{ public static string Describe() { Animal a = new Dog(); Dog d = (Dog)a; return $"Speak via Animal={a.Speak()}, Name via Animal={a.Name()}, Name via Dog={d.Name()}"; }}- Hiding a method with
newwhen you meantoverride.SpeakisvirtualandDogoverrides it, so theAnimal-typed variable still runs the dog's version.Nameis not virtual;Dogmerely hides it withnew, so which body runs depends on the variable's type, not the object's. The compiler warns (CS0108) when you hide without writingnew, and that warning usually marks a bug. - Exposing a public setter "for convenience".
Balance { get; set; }lets any caller write a balance no deposit ever produced. Keep the setter private and give the class methods that enforce its rules. - Asking an object what it is instead of asking it to act. Code like
if (v is Motorbike) fee = Math.Min(fee, 6)outside the class spreads one type's rule across every caller, and the next caller forgets it. The cap belongs insideMotorbike.Fee, where the interface call picks it up automatically.
What interviewers ask
- "Abstract class or interface: when do you pick which?" An abstract class can hold state and constructors but a class may inherit only one; an interface is a contract any number of unrelated types can implement. Pick the interface for a capability, the base class for shared implementation.
Try it yourself
2 visible tests · 4 hidden testsIVehicle declares a Kind property and a Fee(int hours) method.
Implement three classes behind it:
Car:Kindis"Car", and the fee is2.50per hour.Motorbike:Kindis"Motorbike",1.00per hour, but never more than6.00in total.Van:Kindis"Van",4.00per hour, and the first hour is free.
Any hours of zero or less costs 0 for every vehicle. Fees are
decimal.
Then implement PriceList(List<string> kinds, List<int> hours). The
lists line up by position (use only the positions both have). For
"car", "motorbike" and "van" build the matching vehicle, hold it
in an IVehicle variable, and add "<Kind>:<fee>" to the result,
with the fee written with exactly two decimals
(fee.ToString("F2", CultureInfo.InvariantCulture)). Skip any other
kind, including null. Return the list in input order.
PriceList(["car","motorbike","van"], [3,2,3])PriceList(["bus","car",null], [2,1,4])
Sign up to run the tests, check the hidden ones and save your progress. Sign up