Type Systems: Structural vs Nominal
Type Systems
Introduction
In this lesson, you'll learn about type systems: structural vs nominal in C#. Coming from TypeScript, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.
In TypeScript, you're familiar with type systems.
C# has its own approach to type systems, which we'll explore step by step.
The C# Way
Let's see how C# handles this concept. Here's a typical example:
// C#: NOMINAL typing
interface ISerializable {
string Serialize();
}
// Must explicitly implement the interface
class Document : ISerializable {
public string Serialize() => "{}";
}
void Save(ISerializable item) {
item.Serialize();
}
Save(new Document()); // Must be ISerializableComparing to TypeScript
Here's how you might have written similar code in TypeScript:
// TypeScript: STRUCTURAL typing
interface Serializable {
serialize(): string;
}
const obj = {
serialize() { return "{}"; }
};
// Works — obj has the right shape
function save(item: Serializable) {
return item.serialize();
}
save(obj); // OK without explicit declarationYou may be used to different syntax or behavior.
TypeScript structural: any object with the right shape satisfies the interface
You may be used to different syntax or behavior.
C# nominal: class must explicitly declare : IInterfaceName
You may be used to different syntax or behavior.
C# interface names conventionally start with I (IEnumerable, IDisposable)
You may be used to different syntax or behavior.
Both support multiple interface implementation
Step-by-Step Breakdown
1. Explicit Interface Implementation
In C#, a class must declare that it implements an interface with : IInterfaceName. The compiler then verifies all interface members are implemented.
const myObj: Runnable = { run() {} }; // TS: shape is enoughclass MyTask : IRunnable {
public void Run() {} // C#: must declare
}2. Multiple Interface Implementation
Both TypeScript and C# support implementing multiple interfaces. C# syntax mirrors TypeScript's & intersection closely.
interface A { a(): void; }
interface B { b(): void; }
class C implements A, B { ... }interface IA { void A(); }
interface IB { void B(); }
class C : IA, IB {
public void A() {}
public void B() {}
}3. abstract class vs interface
C# abstract classes can have implementation (like TypeScript). Interfaces in C# 8+ can also have default implementations — a feature TypeScript lacks.
abstract class Shape {
abstract area(): number;
describe() { return `area: ${this.area()}`; }
}abstract class Shape {
public abstract double Area();
public string Describe() => $"area: {Area()}";
}4. Type Aliases vs C# type
TypeScript type creates structural aliases. C# has no direct equivalent, but record and using type = ... (C# 12) provide similar conciseness.
type UserId = number;
type Point = { x: number; y: number };// C# 12 type alias:
using UserId = int;
// Record for data shapes:
record Point(double X, double Y);Common Mistakes
When coming from TypeScript, developers often make these mistakes:
- TypeScript structural: any object with the right shape satisfies the interface
- C# nominal: class must explicitly declare : IInterfaceName
- C# interface names conventionally start with I (IEnumerable, IDisposable)
Key Takeaways
- C# is nominally typed — classes must explicitly implement interfaces
- Interface names conventionally start with I in C#
- C# abstract classes and interfaces work identically to TypeScript's
- C# 12 using aliases and records provide TypeScript-like type alias conciseness