Summary#
Two methods, one name, different parameter types:
class Calc {
public decimal Add(decimal a) {
return a;
}
public decimal Add(decimal a, decimal b) {
return a + b;
}
}Each call site picks the one its arguments fit:
[Test]
void Each_Call_Picks_Its_Overload() {
var c = new Calc();
Assert.Equal(1m, c.Add(1m));
Assert.Equal(3m, c.Add(1m, 2m));
}Signature#
class C {
T M(A a) { … } // one overload
T M(A a, B b) { … } // another — different parameter COUNT
T M(B b) { … } // another — different parameter TYPE
}Description#
What makes two overloads different?#
Their parameter types, in order — and nothing else. Two methods that differ only in return type, or only in parameter names, are the same method declared twice, and that is a compile error:
decimal Add(decimal a) { … }
string Add(decimal b) { … } // ✗ same signature — the return type and the name `b` are not differencesThe reason is that a call site cannot act on either one. c.Add(1m) says nothing about what it wants back, and it
does not name the parameter, so there would be no way to say which you meant.
Which overload does a call pick?#
In three steps, stopping as soon as one candidate is left.
1. How many arguments. Only the overloads that your arguments can bind to survive — counting default values, which make a parameter optional, and named arguments, which bind by name rather than position. This is usually the whole story:
[Test]
void Arity_Decides() {
var c = new Calc();
Assert.Equal(5m, c.Add(5m)); // the one-parameter Add
Assert.Equal(9m, c.Add(4m, 5m)); // the two-parameter Add
}2. What type they are. When several overloads take the right number of arguments, the ones whose parameters your arguments actually fit survive:
class Formatter {
public string Show(decimal d) { return "number"; }
public string Show(string s) { return "text"; }
}[Test]
void Type_Decides() {
var f = new Formatter();
Assert.Equal("number", f.Show(1m));
Assert.Equal("text", f.Show("x"));
}3. Which is most specific. If more than one still fits, the one whose parameter type is lower in the class
hierarchy wins — Circle beats Shape:
class Shape { public string Name; }
class Circle : Shape { public decimal Radius; }
class Painter {
public string Draw(Shape s) { return "shape"; }
public string Draw(Circle c) { return "circle"; }
}[Test]
void Most_Specific_Wins() {
var p = new Painter();
Assert.Equal("circle", p.Draw(new Circle { Name = "a", Radius = 1m }));
}It is the DECLARED type that chooses, not the runtime one#
This is the one rule worth reading twice, because it differs from how override works.
Which overload runs is decided when your code is compiled, from the type of the variable you are holding. Which
override runs is decided while it runs, from the type of the object.
[Test]
void The_Declared_Type_Chooses() {
var p = new Painter();
Shape held = new Circle { Name = "a", Radius = 1m };
Assert.Equal("shape", p.Draw(held)); // held is declared `Shape` — even though it holds a Circle
}If you want the object to decide, that is what virtual/override is for — see Class inheritance.
Can a subclass add an overload?#
A subclass inherits its base's overloads and can add to the set. Declaring a method with a new signature adds an
overload; declaring one with an existing signature overrides it (and must say override):
class Reporter {
public virtual string Line(decimal a) { return "base-1"; }
}
class RichReporter : Reporter {
public override string Line(decimal a) { return "rich-1"; } // same signature → an override
public string Line(decimal a, decimal b) { return "rich-2"; } // new signature → a sibling overload
}[Test]
void Adding_And_Overriding() {
var r = new RichReporter();
Assert.Equal("rich-1", r.Line(1m));
Assert.Equal("rich-2", r.Line(1m, 2m));
Reporter asBase = r;
Assert.Equal("rich-1", asBase.Line(1m)); // virtual dispatch still finds the override
}base.Line(…) picks from the base's set the same way, so an override of one overload can still call either.
When a call is ambiguous#
If two overloads both fit and neither is more specific, the call is refused rather than guessed. Say which you mean by giving the argument a declared type:
class P {
string Go(Left? l, Right? r) { … }
string Go(Right? r, Left? l) { … }
}
p.Go(null, null); // ✗ ambiguous — both fit, neither is more specific
Left? l = null;
p.Go(l, null); // ✓ the declared type of `l` settles itA method and a property cannot share a name#
Only methods overload. A property has no overload set to join, so a method named after a property is refused — one of the two has to be renamed.
Constructors do not overload yet#
A class declares one constructor. Give it the widest parameter list and default the ones a caller may omit:
public Box(decimal width, decimal height = 1m) { … } // one constructor, two ways to call itSee also#
- class methods — declaring methods, default values, and named arguments
- Class inheritance —
virtual/override, and why they decide at a different moment than overloads do - constructor — the single constructor, and defaulting its parameters
- Classes — what a class is