次の方法で共有


Color Color

Внезапный опрос: что делает следующий код при компиляции и исполнении?

class C
{
    public static void M(string x)
    {
        System.Console.WriteLine("static M(string)");
    }
    public void M(object s)
    {
        System.Console.WriteLine("M(object)");
    }
}
class Program
{
    static void Main()
    {
        C c = new C();
        c.M("hello");
    }
}

  1. Выводит «static M(string)»
  2. Выводит «M(object)»
  3. Э, чувак, это даже не скомпилируется, уж тем более не запустится
  4. Что-то еще

Подумайте над этим немножко, а потом проверьте и посмотрите, правы ли вы.

.

.

.

.

.

.

.

В варианте (1), компилятор мог бы решить, что лучший выбор – статический метод, и позволить вам вызвать его через экземпляр, игнорируя значение экземпляра. В варианте (2), компилятор мог бы решить что экземплярная версия – лучший выбор, и проигнорировать статическую, несмотря на лучшее соответствие аргумента. Но ни один из этих вариантов в действительности не происходит; правила C# гласят, что компилятор должен выбирать лучший метод, основываясь на аргументах, а потом запрещать вызов статического через экземпляр! Так что фактический результат здесь (3):

error CS0176: Member 'C.M(string)' cannot be accessed with an instance reference; qualify it with a type name instead

Что это за безумие? Если вы считаете, что правило «к статическим методам ни в коем случае нельзя обращаться через экземпляры» - хорошее правило, - то почему алгоритм разрешения перегрузок не удаляет статические методы из списка кандидатов при вызове через экземпляр? Почему он вообще позволяет «string»-версии быть применимым кандидатом, если компилятор знает, что это никак не может сработать?

Я согласен, что на первый взгляд это выглядит действительно неуклюже.

Для объяснения того, что это не так глупо, как кажется, рассмотрите следующую вариацию задачи. Класс C остаётся таким же.

class B
{
  public C C = new C();
  public void N()
  {
    C.M("hello");
  }
}

Что даёт вызов N на экземпляре B?

  1. Вывод «static M(string)»
  2. Вывод «M(object)»
  3. Ошибку компиляции
  4. Что-то ещё

.

.

.

.

.

.

Теперь немножко посложнее, не так ли? Означает ли C.M «вызови экземплярный метод M на объекте, который хранится в this.C», или же это означает «вызови статический метод M класса C»? Оба подходят!

Из-за нашего неуклюжего правила мы в этом случае делаем то, что нужно. Сначала мы выбираем метод, основываясь только на аргументах, и выясняем, что статический является лучшим выбором. Затем мы проверяем, можно ли интерпретировать «получателя» как тип. Можно. Так что мы вызываем статический метод и выводим «static M(string)». Если бы лучшим выбором оказалась экземплярная версия, то мы бы успешно вызвали экземплярный метод у значения свойства.

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

Теперь рассмотрим аргумент, что в нашем первом случае получатель не может быть типом. Мы могли бы усложнить семантику языка, введя три правила разрешения перегрузок: одно – для случая, когда известно, что получатель – тип, одно – для получателя, который точно не тип, и еще одно – для случая, когда получатель может как быть, так и не быть типом. Но вместо дальнейшего усложнения языка, мы сделали прагматичный выбор просто откладывать проверку статичности до окончания проверки наилучшести. Это не идеальное решение, но оно достаточно хорошо для большинства случаев и решает типичную проблему.

Типичная проблема – ситуация, когда у вас есть свойство с именем, совпадающим с его типом, и вы пытаетесь вызвать либо статический метод типа, либо экземплярный метод у свойства. (Это также может происходить с переменными и полями, но обычно имена переменных и полей отличаются регистром от имён их типов.) Канонический мотивирующий пример – это публичное свойство Color типа Color, так что мы называем это «проблемой Color Color».

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

Если вам интересно почитать точные правила того, как компилятор решает проблему Color Color, посмотрите в секцию 7.5.4.1 спецификации C# 3.0.