This document shows side-by-side Dart vs C# examples and maps Dart pattern matching concepts directly to C# mental models.
1. Sealed hierarchy + exhaustive switch
Dart
sealed class Shape {}
class Circle extends Shape {
final double r;
Circle(this.r);
}
class Rectangle extends Shape {
final double w, h;
Rectangle(this.w, this.h);
}
double area(Shape s) => switch (s) {
Circle(r: var r) => 3.14 * r * r,
Rectangle(w: var w, h: var h) => w * h,
};
C#
sealed abstract class Shape;
sealed class Circle(double R) : Shape;
sealed class Rectangle(double W, double H) : Shape;
double Area(Shape s) => s switch
{
Circle { R: var r } => Math.PI * r * r,
Rectangle { W: var w, H: var h } => w * h
};
Mental model:
Dart
Dart
Dart
Dart
sealed class ≈ C# sealed abstract base + sealed subclasses.Dart
switch expression ≈ C# switch expression with no default.Dart
sealed class Shape marks Shape as a closed hierarchy. All subclasses must be declared in the same library. The compiler knows every possible subtype of Shape at compile time.
2. Object destructuring (fields vs properties)
Dart
case User(name: 'Alice', age: var age):
C#
case User { Name: "Alice", Age: var age }:
Mental model:
• Dart matches fields directly
• Thus Dart can access private _variables within the same library
• C# matches properties
• Dart feels data-centric
• C# feels API-centric
• Dart matches fields directly
• Thus Dart can access private _variables within the same library
• C# matches properties
• Dart feels data-centric
• C# feels API-centric
3. Records / tuples
Dart (records are first-class)
(String, int) user = ('Bob', 42);
switch (user) {
case ('Bob', var age):
print(age);
}
C# (tuples)
(string, int) user = ("Bob", 42);
switch (user)
{
case ("Bob", var age):
Console.WriteLine(age);
break;
}
Mental model:
Dart record ≈ C# tuple or
Dart records are first-class, meaning they are fully typed, can be stored in variables, passed to functions, returned from functions, and destructured with pattern matching just like any other Dart type.
Dart record ≈ C# tuple or
record, but Dart records are structural and named by default.Dart records are first-class, meaning they are fully typed, can be stored in variables, passed to functions, returned from functions, and destructured with pattern matching just like any other Dart type.
4. Type narrowing (if-case vs is)
Dart
if (value case String s) {
print(s.toUpperCase());
}
C#
if (value is string s)
{
Console.WriteLine(s.ToUpper());
}
Mental model:
Dart
Dart
if (x case T v) ≈ C# if (x is T v).
5. Guards / conditions
Dart
switch (n) {
case int x when x > 0:
print('positive');
}
C#
switch (n)
{
case int x when x > 0:
Console.WriteLine("positive");
break;
}
Mental model:
Dart
Dart
when ≈ C# when. Guards narrow matches and do not make them exhaustive.
6. List patterns
Dart
switch (list) {
case [1, 2, _]:
print('starts with 1,2');
}
C#
if (list is [1, 2, _])
{
Console.WriteLine("starts with 1,2");
}
Mental model:
Dart list patterns ≈ C# list patterns (C# 11+). C# offers more slicing features.
Dart list patterns ≈ C# list patterns (C# 11+). C# offers more slicing features.
7. Partial vs exhaustive patterns
Dart
sealed class Result {}
class Ok extends Result {
final int v;
Ok(this.v);
}
class Err extends Result {
final String msg;
Err(this.msg);
}
switch (result) {
case Ok(v: > 0):
print('positive');
case Ok():
print('non-positive');
case Err():
print('error');
}
C#
sealed abstract class Result;
sealed class Ok(int V) : Result;
sealed class Err(string Msg) : Result;
switch (result)
{
case Ok { V: > 0 }:
Console.WriteLine("positive");
break;
case Ok:
Console.WriteLine("non-positive");
break;
case Err:
Console.WriteLine("error");
break;
}
Mental model:
Guarded patterns are partial; the base pattern must still be handled.
Guarded patterns are partial; the base pattern must still be handled.
Dart → C# Mental Model Mapping
| Dart Concept | C# Mental Model |
|---|---|
sealed class |
sealed abstract class |
| Record | Tuple or record |
| Object pattern (looks to fields) | Property pattern |
if (x case P) |
if (x is P) |
switch expression |
switch expression |
| Exhaustive switch | Sealed + no default |
| Pattern variables | Pattern variables |
when |
when |
| No fallthrough | Expression switch |
Philosophical Difference
Dart
Patterns focus on data shape:
- Less ceremony
- Strong exhaustiveness guarantees
- Ideal for UI and state modeling
C#
Patterns focus on logical constraints:
- Highly expressive
- C#: more ceremony meaning you have to define supporting methods, use { } property syntax, add breaks, respect public-only access.
- Rich relational and logical operators
- Ideal for business rules and validation
Translation Guide (TL;DR)
If you understand C# pattern matching, Dart's patterns are the same ideas with fewer knobs: sealed classes replace defensive defaults, records replace ad-hoc DTOs, and exhaustiveness is enforced more eagerly.