Почему статический и нестатический метод не могут использовать одну и ту же подпись?

С# обеспечивает следующие характеристики подписи, которые будут использоваться при перегрузке функций.

Мы знаем, что для перегрузки учитываются только аргументы; их количество и типы, но цель полиморфизма - предоставить одно и то же имя, но разное использование в зависимости от стратегии вызова.

Если у меня есть класс, содержащий два метода с одинаковым именем и сигнатурой, в то время как один является статическим, а другой нет, компилятор С# выдает ошибку; "Класс уже определяет член с именем" foo "с одинаковыми типами параметров". Вызов обоих методов будет различным; один с именем объекта и статический с именем класса. Следовательно, нет никакой двусмысленности со стратегией вызова. Тогда почему выдает ошибку?

 class Example {

    public void foo() { }
    public static void foo() { }

}

class Program
{
    static void Main(string[] args)
    {

        Example e = new Example();
        e.foo(); 

    }
}

Ответ 1

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

public class Foo()
{
   public static void MyMethod() {};
   public void MyMethod() {}

   public void SomeOtherMethod()
   {
      MyMethod(); // which method we're calling static or non-static ?
   }
}

ИЗМЕНИТЬ

Только что нашел этот сообщение СОСТОЯНИЯ относительно вашего дела. Вы также можете проверить его.

Ответ 2

Эта ошибка возникает, потому что так определяется поведение в С# Language Specification. Любое "двусмысленное" использование (или способы устранения неоднозначности таких) не имеет значения, хотя такие рассуждения и краевые случаи, возможно, привели к тому, что дизайнеры не допускали явного разрешения такой дифференциации.. или это может быть просто кодирование С# для базового .NET CLI/Ограничение CLR 1.

От "3.6 Подписи и перегрузка" в спецификации С# (и в соответствии со связанной документацией), отформатированной как bullets:

Подпись метода состоит из

  • имя метода,
  • количество параметров типа и
  • тип и вид (значение, ссылка или вывод) каждого из его формальных параметров.

Модификаторы метода, включая static, здесь не рассматриваются как часть сигнатуры метода.

И, из "1.6.6 Методов" мы имеем ограничение и соглашающееся резюме:

Подпись метода должна быть уникальной в классе, в котором объявлен метод. Подпись метода состоит из имени метода, количества параметров типа и количества, модификаторов и типов его параметров.

Это ограничение применяется до (и независимо от) метода, рассматриваемого для полиморфизма.

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


1 Существует поддержка этого, просто являющаяся результатом ограничения самого CLI/CLR.NET, чего не стоит обойти (т.е. по причинам совместимости). Из "I.8.6.1.5 Подписи метода" в ECMA-335:

Подпись метода состоит из

  • соглашение о вызове [CLS Rule 15: "единственное соглашение о вызове поддерживаемый CLS, является стандартным управляемым вызовом",
  • количество общих параметров, если метод является общим,
  • [опущено правило]
  • список нулевых или более сигнатур параметров - по одному для каждого параметра метода - и
  • подпись типа для значения результата, если он создан.

Подписи методов объявляются определениями методов. Только одно ограничение может быть добавлено к в дополнение к сигнатурам параметров [CLS Правило 15: "Ограничение vararg не является частью CLS" ]:

  • Ограничение vararg может быть включено, чтобы указать, что все аргументы, прошедшие эту точку, необязательный. Когда он появится, вызывающим соглашением будет тот, который поддерживает переменную списки аргументов.

Таким образом, пересечение между компонентами сигнатуры С#/CLS и ECMA является именем метода, "количеством общих параметров" и "списком нулевых или более сигнатур параметров".

Ответ 3

Я чувствую, что ваш вопрос: "Почему стандарт решил запретить объявление двух методов, которые отличаются только ключевым словом static?", ​​и поэтому ответ "потому что стандарт говорит так" не подходит мне.

Теперь проблема в том, что может быть какая-то причина. Стандартом является Закон, и он может быть произвольным. Без помощи того, кто участвовал в разработке языка, все, что мы можем сделать, это размышлять о причинах, пытаясь раскрыть дух Законов.

Вот моя догадка. Я вижу три основные причины этого выбора:

Потому что говорят другие языки.

С++ и Java являются вдохновляющими языками для С#, и имеет смысл наблюдать те же правила перегрузки, что и эти языки. Что касается того, почему так на этих языках, я не знаю. Я нашел аналогичный вопрос о SO о С++, хотя не дается ответа на вопрос, почему именно так (за пределами "стандарт говорит так" ).

Потому что это создает неоднозначность, которая должна быть решена.

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

Потому что это не имеет большого смысла в ООП.

Это действительно мое понимание здесь, поэтому я могу быть абсолютно неправым (по крайней мере, @user2864740 считает, что аргумент сомнительный - см. комментарии), но я чувствую, что статические члены - это способ введения "функционального программирования" в ООП, Они не привязаны к конкретному экземпляру, поэтому они не изменяют внутреннее состояние объекта (если они изменяют состояние другого объекта, то они должны быть нестационарным методом этого другого объекта), таким образом, они являются "чистыми". Поэтому я не понимаю, как "чистая функция" может быть семантически близка к регулярному объекту, так что они будут иметь одно и то же имя.

Ответ 4

Тот же вопрос был задан Эрику Ганнерсону, который работал в команде разработчиков языка С#, и он ответил:

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

Поэтому причина, по которой это не разрешено, заключается в дизайне.

Ответ 5

Проверьте этот простой псевдокод:

class A
{
  public void B(){...}
  public static void B(){...}
}

...

A A = new A();
A.B();  // <== which one is going to be called?