Overload resolution, conversions, and patterns
How the compiler picks a member: betterness rules, user-defined conversions, pattern matching mechanics.
Overload resolution, conversions, and patterns
When you call a method, Roslyn runs a deterministic search: find applicable candidates, then apply the betterness rules. You do not need the spec in your head โ you need the handful of rules that decide real bugs:
- Exact type beats implicit conversion.
Pick(5)prefersPick(int)overPick(long). - Non-generic beats generic when both apply without conversion;
a fixed type argument beats both (
Pick<object>(5)calls the generic). - Implicit numeric widening never happens implicitly in overload choice
between
intandlongarguments โ the int literal binds toint. - User-defined conversions are consulted last and never chain.
Pattern matching is compiled to type checks and value extraction:
static string Describe(object o) => o switch
{
int n when n < 0 => "negative",
int and > 100 => "big int",
string { Length: 0 } => "empty string",
Point(_, 0) => "on the X axis",
null => "null",
_ => "other",
};
switch expressions compile to a decision tree โ isinst/unbox for type
patterns, comparisons for relational patterns. Recursive patterns nest
declarations, not behavior. The design win: exhaustive-looking shapes push
state decisions out of if-ladders into one declaration the compiler can
check (add a case for a new enum member and the _ discards it silently โ
that silence is the tax).
When patterns go wrong: matching on object erases generic type info
(rely on when guards, not on runtime surprises), and is type patterns
on value types box the operand โ one more silent allocation on hot paths.