Method Overloading in C#: How Signatures Work

Method overloading in C# enables a class to define multiple methods with the exact same name, as long as each method possesses a distinct signature. At compile time, the C# compiler evaluates the arguments provided during a method call to determine and bind the execution to the most specific matching version. This guide covers the precise mechanics of method signatures, what variations the compiler accepts, and the common pitfalls that cause ambiguity or compilation errors.

What Constitutes a Method Signature?

In C#, a method signature consists exclusively of:

  1. The method name.
  2. The number (arity) of parameters.
  3. The data types of the parameters.
  4. The order of the parameter types.
  5. Parameter modifiers (specifically ref, out, and in).

Elements such as the return type, parameter names, and access modifiers (public, private, etc.) are not part of the method signature.

Valid Signature Differences

To successfully overload a method, the parameter list must differ in at least one of the following ways:

1. Number of Parameters

The simplest way to overload is by varying the parameter count.

public class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Add(int a, int b, int c) => a + b + c;
}

2. Types of Parameters

Methods can accept the same number of arguments, provided their data types differ.

public class Printer
{
    public void Print(string message) => Console.WriteLine(message);
    public void Print(int number) => Console.WriteLine(number);
}

3. Order of Parameter Types

If two methods share the same parameter types, varying the sequence creates distinct signatures.

public class Display
{
    public void Render(string label, int count) { }
    public void Render(int count, string label) { }
}

4. Parameter Modifiers (ref, out, in vs. Value)

The compiler treats pass-by-reference modifiers as distinct from standard pass-by-value parameters.

public class Processor
{
    public void Process(int data) { }
    public void Process(ref int data) { } // Valid overload
}

Invalid Signature Differences

Attempting to overload methods based solely on the following factors will cause a compile-time error:

  • Return Types: You cannot overload a method based exclusively on what it returns. The signature does not include the return type because a caller can execute a method without capturing its return value, leaving the compiler unable to determine which method to invoke.

    // Compiler Error: CS0111
    public int GetData() => 42;
    public string GetData() => "42";
  • Parameter Names: Renaming parameters without changing their types does not alter the signature.

    // Compiler Error: CS0111
    public void Log(string message) { }
    public void Log(string text) { }
  • ref vs. out: While value vs. reference is a valid distinction, you cannot overload a method where the only difference between two signatures is ref versus out (or in). The runtime treats both internally as managed pointers.

    // Compiler Error: CS0663
    public void Update(ref int value) { }
    public void Update(out int value) { value = 0; }

Compile-Time Resolution and Ambiguity

Method overloading is an implementation of compile-time (static) polymorphism. When a call occurs, the compiler uses overload resolution to pick the best candidate:

  1. Exact Match: The compiler searches for a method whose parameter types match the argument types exactly.
  2. Implicit Conversions: If an exact match is missing, the compiler evaluates candidates that accept arguments via implicit type conversion (e.g., passing an int to a parameter typed as double).
  3. Ambiguity Errors: If two or more methods are equally viable after implicit conversions—or when optional parameters overlap with existing signatures—the compiler throws a CS0121 error (the call is ambiguous). In these cases, explicit casting at the call site is required to specify the intended overload.