Classes, properties, and interfaces
Auto-properties instead of hand-written getters, and what an interface promises that a base class does not.
After this lesson you can
- Write a class with auto-implemented properties and a constructor
- Say what an interface can require that an abstract class cannot
- 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 IShape
{
double Area();
}
public class Circle : IShape
{
public double Radius { get; init; }
public double Area() => Math.Round(Math.PI * Radius * Radius, 2);
}
public class Square : IShape
{
public double Side { get; init; }
public double Area() => Side * Side;
}
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, with no state of its own.
Polymorphism through the interface
List<IShape> shapes = new() { new Circle { Radius = 2 }, new Square { Side = 3 } };
double total = shapes.Sum(s => s.Area());
The loop — or here, Sum — never needs to know which concrete type
each IShape actually is. Which Area() runs is decided at runtime by
the object's real type, which is exactly what makes adding a
Triangle : IShape later free at every place that already only ever
depended on IShape.
Try it yourself
2 visible tests · 2 hidden testsIShape declares Area(). Circle and Square both implement it —
Circle.Area() should be Math.Round(Math.PI * Radius * Radius, 2),
Square.Area() should be Side * Side. Implement
TotalArea(List<double> circleRadii, List<double> squareSides): build
one Circle per radius and one Square per side, and return the sum
of every shape's Area(), rounded to 2 decimal places.
TotalArea([2], [3])TotalArea([], [1,2,3])
Sign up to check the hidden tests and save your progress. Sign up