Learn to move shared features into a base class, polymorphism with virtual/override, and defining contracts with interface, using a sensor example.
Inheritance: put what is common in one place
A temperature sensor, a pressure sensor, a humidity sensor... All of them have a name and all of them read a value. Instead of copying the common part into every class, you write it in a base class and derive the others from it. A derived class inherits the members of the base class.
class Sensor
{
public string Name { get; }
public Sensor(string name) { Name = name; }
public virtual double Read()
{
return 0;
}
public override string ToString() => $"{Name}: {Read():F1}";
}
class TemperatureSensor : Sensor
{
public TemperatureSensor(string name) : base(name) { }
public override double Read()
{
return 23.5; // in reality it is read from hardware
}
}
class HumiditySensor : Sensor
{
public HumiditySensor(string name) : base(name) { }
public override double Read() => 48.2;
}
Two markers: : Sensor means "derives from Sensor". base(name) calls the constructor of the base class. The virtual keyword in the base class is the permission "derived classes may change this method", and override in the derived class is the actual change.
Polymorphism
The real power is here: you keep different types together under the common base class type and make the same call; each object applies its own behavior.
var sensors = new List<Sensor>
{
new TemperatureSensor("LM35"),
new HumiditySensor("DHT22")
};
foreach (Sensor s in sensors)
{
Console.WriteLine(s); // ToString -> Read()
}
LM35: 23.5
DHT22: 48.2
When you add a new sensor type to the list, you don't need to change this loop. This is one of the core ideas that makes large software manageable.
Abstract classes
"Sensor" is not a thing on its own; it is only a concept. If you want it to be impossible to create an object of it directly, and want derived classes to be forced to write the Read method, use abstract:
abstract class Sensor
{
public string Name { get; }
protected Sensor(string name) { Name = name; }
public abstract double Read(); // no body, the derived class fills it in
}
Now if you write new Sensor("x"), you get a compile error, and a derived class that doesn't write Read won't compile. The protected constructor also shows that only derived classes can call it.
Interfaces
An interface is a contract: "every class that implements this interface has these members". There is no work done inside it, only signatures. A class can derive from only one class but can implement several interfaces. By convention, names start with I.
var v = new Voltmeter();
v.Calibrate(0.05);
Console.WriteLine(v.LogLine());
interface ICalibratable
{
void Calibrate(double offset);
}
interface ILoggable
{
string LogLine();
}
class Voltmeter : ICalibratable, ILoggable
{
private double _offset;
public void Calibrate(double offset) => _offset = offset;
public double Read() => 3.30 + _offset;
public string LogLine() => $"VOLT;{Read():F2}";
}
VOLT;3.35
The leading underscore (_offset) is a common naming habit for private fields.
is and as: asking about the type
Sensor s = new TemperatureSensor("LM35");
if (s is TemperatureSensor temperature)
{
Console.WriteLine($"This is a temperature sensor: {temperature.Name}");
}
Which one? If there is an "X is a Y" relationship, use inheritance (a TemperatureSensor is a Sensor). If there is an "X can do this" relationship, use an interface (a Voltmeter can be calibrated). Don't overuse inheritance just to share something small; an interface is often more flexible.
Warning: To be able to override a method, the method in the base class must be virtual or abstract. Otherwise the compiler gives an error.
Try it yourself
Write an abstract Shape class (with an abstract Area() method). Derive Circle and Rectangle classes from it. Put mixed shapes into a list and calculate the total area. Then define an interface called IPrintable and make one of the shapes implement it.
// Mini quiz
Click an option; you will see right away, with the reason, whether it is correct or not.
Mark it when you finish: your progress is saved and you earn +20 XP.
Tip: ← → keys let you move between lessons.