С#: Передача null в перегруженный метод - какой метод вызывается?

Скажем, у меня есть две перегруженные версии метода С#:

void Method( TypeA a ) { }
void Method( TypeB b ) { }

Я вызываю метод с помощью:

Method( null );

Какую перегрузку метода вызывают? Что я могу сделать, чтобы обеспечить определенную перегрузку?

Ответ 1

Это зависит от TypeA и TypeB.

  • Если используется одно из них (например, нет преобразования из null в TypeB, потому что это тип значения, но TypeA является ссылочным типом), то вызов будет сделан применимым.
  • В противном случае это зависит от отношения между TypeA и TypeB.
    • Если существует неявное преобразование из TypeA в TypeB, но не подразумевается преобразование с TypeB в TypeA, тогда будет использоваться перегрузка с использованием TypeA.
    • Если существует неявное преобразование из TypeB в TypeA, но не подразумевается преобразование с TypeA в TypeB, тогда будет использоваться перегрузка с использованием TypeB.
    • В противном случае вызов неоднозначен и не будет компилироваться.

См. раздел 7.4.3.4 спецификации С# 3.0 для подробных правил.

Вот пример того, что он не двусмыслен. Здесь TypeB происходит от TypeA, что означает наличие неявного преобразования от TypeB до TypeA, но не наоборот. Таким образом, используется перегрузка с использованием TypeB:

using System;

class TypeA {}
class TypeB : TypeA {}

class Program
{
    static void Foo(TypeA x)
    {
        Console.WriteLine("Foo(TypeA)");
    }

    static void Foo(TypeB x)
    {
        Console.WriteLine("Foo(TypeB)");
    }

    static void Main()
    {
        Foo(null); // Prints Foo(TypeB)
    }
}

В общем, даже перед лицом неоднозначного вызова, чтобы обеспечить конкретную перегрузку, просто нажмите:

Foo((TypeA) null);

или

Foo((TypeB) null);

Обратите внимание, что если это связано с наследованием в объявляющих классах (т.е. один класс перегружает метод, объявленный его базовым классом), вы попадаете в целую другую проблему, и вам нужно указать цель метода, а не аргумент.

Ответ 2

Jon Skeet дал исчерпывающий ответ, но с точки зрения дизайна вы не должны зависеть от угловых случаев спецификации компилятора. Если ничего другого, если вам нужно посмотреть, что он делает, прежде чем писать, следующий человек, который попытается прочитать его, не будет знать, что он делает. Он не поддерживается.

Перегрузки существуют для удобства, и две разные перегрузки с одинаковым именем должны делать то же самое. Если эти два метода делают разные вещи, переименуйте один или оба из них.

Более привычно для перегруженного метода иметь варианты с различным количеством параметров, а для перегрузки с меньшими параметрами для обеспечения разумных значений по умолчанию.

например. string ToString(string format, System.IFormatProvider provider) имеет большинство параметров,
string ToString(System.IFormatProvider provider) предоставляет формат по умолчанию и
string ToString() предоставляет формат и поставщик по умолчанию,

Ответ 3

Jon Skeet уже ответил, какая перегрузка выбрана по умолчанию, но если вы хотите, чтобы вызывалась определенная перегрузка, часто лучше использовать именованные параметры, чем приведение.

Если у вас есть:

void Method( TypeA a ) { }
void Method( TypeB b ) { }

Вы можете вызвать Method(a: null); или Method(b: null);

Ответ 4

двусмысленный вызов. (ошибка времени компиляции).

Ответ 5

Простым решением является создание другого метода с сигнатурой:

void Method() { }

или лучше обновить подпись на одном из методов:

void Method( TypeB b = null ) { }

а затем назовите его так:

Method();

Во время компиляции значение null является нетипизированным, поэтому компилятор не может сопоставить его с одной из сигнатур метода. Во время выполнения все переменные, которые могут быть разрешены на null, будут набираться и поэтому не вызовут проблемы.