Dart vs C# Pattern Matching

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 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

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 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 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 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.

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.

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.