Method Overloading — Multiple Signatures, One Name

Method overloading means defining multiple methods with the same name in the same class, but with different parameter lists. The compiler chooses which overload to call based on the types and number of arguments at the call site — this is called overload resolution. Overloading is how Console.WriteLine accepts a string, an int, a double, a bool, or any object; how string.Format takes 1, 2, or 3 parameters; and how ASP.NET Core’s Ok(), BadRequest(), and NotFound() helpers accept either no argument or a value. It is a fundamental .NET pattern.

Defining Overloads

public class TextFormatter
{
    // Overload 1 — just the text
    public string Format(string text)
        => text.Trim();

    // Overload 2 — text plus max length
    public string Format(string text, int maxLength)
    {
        text = text.Trim();
        return text.Length <= maxLength
            ? text
            : text[..maxLength] + "…";
    }

    // Overload 3 — text, max length, and suffix
    public string Format(string text, int maxLength, string suffix)
    {
        text = text.Trim();
        return text.Length <= maxLength
            ? text
            : text[..(maxLength - suffix.Length)] + suffix;
    }

    // Overload 4 — different parameter type (DateTime)
    public string Format(DateTime date)
        => date.ToString("yyyy-MM-dd");

    public string Format(DateTime date, string formatString)
        => date.ToString(formatString);
}

var formatter = new TextFormatter();
formatter.Format("  Hello World  ");              // "Hello World"
formatter.Format("Hello World", 5);              // "Hello…"
formatter.Format("Hello World", 8, "...");       // "Hello..."
formatter.Format(DateTime.Now);                  // "2025-06-15"
formatter.Format(DateTime.Now, "dd/MM/yyyy");    // "15/06/2025"
Note: Overload resolution — choosing which method to call — is performed entirely by the compiler at compile time. The compiler picks the most specific overload that matches the argument types. If there is ambiguity (two overloads that are equally specific), it is a compile error, not a runtime error. This means overloads cannot differ only in return type — the compiler cannot use the return type to choose between overloads because it does not always know what type the caller expects.
Tip: A common pattern is to have overloads delegate to the most general overload, avoiding code duplication: public string Format(string text) => Format(text, int.MaxValue, string.Empty);. This ensures all validation and core logic lives in one place — the full overload — and simpler overloads are just convenient entry points. This is called the “telescoping constructor / method” pattern and is used throughout the .NET BCL.
Warning: Overloads cannot differ only in ref vs non-ref (ambiguity) or only in return type (compiler cannot distinguish). Also be careful with numeric type overloads — C# will silently widen an int to match a long overload if there is no exact int match, which can cause unexpected overload selection. Avoid having overloads with the same parameter count where types are implicitly convertible to each other; the resulting call resolution can be surprising.

Overloading vs Optional Parameters

// Optional parameters approach — fewer lines, one method
public string Format(string text, int maxLength = int.MaxValue, string suffix = "…")
{
    text = text.Trim();
    if (maxLength == int.MaxValue) return text;
    return text.Length <= maxLength
        ? text
        : text[..(maxLength - suffix.Length)] + suffix;
}

// Overloads approach — cleaner individual signatures, better IntelliSense
// Use overloads when:
//   - Public API that may change (adding optional params is a breaking change
//     in some versioning scenarios)
//   - Different overloads have meaningfully different behaviour
//   - You want separate XML documentation for each signature

// Use optional parameters when:
//   - Internal/private methods with simple defaults
//   - The defaults are stable and unlikely to change
//   - Fewer methods to maintain is the priority

Common Mistakes

Mistake 1 — Overloads that differ only in return type

❌ Wrong — compile error (overloads cannot differ only by return type):

public int    GetValue() => 42;
public string GetValue() => "42";   // compile error!

Mistake 2 — Ambiguous overloads with numeric types

❌ Confusing — which overload is called?

public void Process(int n) { }
public void Process(long n) { }
Process(42);   // calls int overload (exact match)
Process(42L);  // calls long overload (L suffix)

When in doubt, use named arguments or explicit casts to make the intended overload clear.

🧠 Test Yourself

You have overloads void Log(string msg) and void Log(string msg, LogLevel level). The first calls the second with a default level. Where should the actual logging logic live?