Object-Oriented Programming
C# classes, inheritance, interfaces vs C structs and function pointers
Introduction
In this lesson, you'll learn about object-oriented programming in C#. Coming from C, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In C, you're familiar with c# classes, inheritance, interfaces vs c structs and function pointers.
C# has its own approach to c# classes, inheritance, interfaces vs c structs and function pointers, which we'll explore step by step.
The C# Way
Let's see how C# handles this concept. Here's a typical example:
using System;
// Interface — like C's function pointer table
public interface IShape
{
double Area();
void Print();
// Default implementation (C# 8+)
string Describe() => $"Area={Area():F2}";
}
// Abstract class — like C's base struct with some implementations
public abstract class ShapeBase : IShape
{
public abstract double Area();
public virtual void Print() => Console.WriteLine(Describe());
}
// Concrete class — like C's specific struct + functions
public class Circle : ShapeBase
{
public double Radius { get; }
public Circle(double r) => Radius = r;
public override double Area() => Math.PI * Radius * Radius;
public override void Print() => Console.WriteLine($"Circle(r={Radius:F1})");
}
// Multiple interface implementation
public class Rectangle : ShapeBase, IComparable<Rectangle>
{
public double W { get; } public double H { get; }
Rectangle(double w, double h) { W = w; H = h; }
public override double Area() => W * H;
public int CompareTo(Rectangle? other) => Area().CompareTo(other?.Area());
}
// Polymorphism via interface type
void Describe(IShape s) {
s.Print();
Console.WriteLine(s.Describe());
}
Describe(new Circle(5));Comparing to C
Here's how you might have written similar code in C:
#include <stdio.h>
#include <math.h>
// OOP simulation in C
typedef struct Shape Shape;
struct Shape {
double (*area)(const Shape *);
void (*print)(const Shape *);
};
typedef struct {
Shape base; // "inheritance" via embedding
double radius;
} Circle;
double circle_area(const Shape *s) {
const Circle *c = (const Circle *)s;
return M_PI * c->radius * c->radius;
}
void circle_print(const Shape *s) {
const Circle *c = (const Circle *)s;
printf("Circle(r=%.1f)\n", c->radius);
}
void init_circle(Circle *c, double r) {
c->radius = r;
c->base.area = circle_area;
c->base.print = circle_print;
}
// Polymorphism via function pointer
void describe(const Shape *s) {
s->print(s);
printf(" area=%.2f\n", s->area(s));
}You may be used to different syntax or behavior.
C# classes are true OOP — no manual function pointer tables or casting needed
You may be used to different syntax or behavior.
interface defines contract; abstract class provides partial implementation
You may be used to different syntax or behavior.
override keyword is explicit — prevents accidental method hiding
You may be used to different syntax or behavior.
A class can implement multiple interfaces — no C's embedding tricks
You may be used to different syntax or behavior.
Properties (get/set) replace C's field + getter/setter function pattern
Step-by-Step Breakdown
1. Class vs Struct
C# classes are reference types on the heap — like C's malloc'd struct pointers. C# also has struct (value type) for stack-allocated small data.
Circle *c = malloc(sizeof(Circle));
init_circle(c, 5.0);var c = new Circle(5.0); // heap allocated (class)
// Small value types:
struct Point { double X, Y; } // stack allocated2. Interface
C# interfaces replace C's function pointer tables. Any class implementing IShape can be used as IShape — no casting needed.
typedef struct Shape {
double (*area)(const Shape *);
} Shape;
// Upcast: Circle* → Shape*interface IShape { double Area(); }
class Circle : IShape { public double Area() => Math.PI*r*r; }
IShape s = new Circle(5); // no cast needed
s.Area(); // calls Circle.Area()3. Inheritance
C# inheritance is cleaner than C's struct embedding. override is explicit — the compiler prevents silent method hiding.
typedef struct { Shape base; double r; } Circle;
// Must cast to call base functions: ((Shape*)&c)->area(&c)class Animal {
public virtual void Speak() => Console.Write("...");
}
class Dog : Animal {
public override void Speak() => Console.Write("Woof");
}
Animal a = new Dog();
a.Speak(); // "Woof" — virtual dispatch4. Properties
C# properties replace C's field + getter/setter function pattern. Auto-properties generate the backing field automatically.
typedef struct { double _r; } Circle;
double circle_get_r(const Circle *c) { return c->_r; }
void circle_set_r(Circle *c, double r) { c->_r = r; }class Circle {
// Auto property
public double Radius { get; private set; }
// Computed property
public double Area => Math.PI * Radius * Radius;
// Property with validation
private double _r;
public double R {
get => _r;
set => _r = value > 0 ? value : throw new ArgumentException();
}
}Common Mistakes
When coming from C, developers often make these mistakes:
- C# classes are true OOP — no manual function pointer tables or casting needed
- interface defines contract; abstract class provides partial implementation
- override keyword is explicit — prevents accidental method hiding
Key Takeaways
- Classes are reference types (heap); structs are value types (stack) — no malloc needed
- interface replaces C function pointer tables; implements without casting
- override is explicit — prevents accidental method hiding
- Properties (get/set) replace C getter/setter function patterns