Разрешение метода С#, long vs int

class foo
{
  public void bar(int i) { ... };
  public void bar(long i) { ... };
}


foo.bar(10);

Я ожидал, что этот код выдаст мне какую-то ошибку или, по крайней мере, предупреждение, но не так...

Какая версия bar() вызывается и почему?


person Vegar    schedule 25.05.2011    source источник


Ответы (3)


Вызывается int-версия bar, потому что 10 является литералом int, и компилятор будет искать метод, который ближе всего соответствует входной переменной (переменным). Чтобы вызвать длинную версию, вам нужно указать длинный литерал, например: foo.bar(10L);

Вот сообщение Эрика Липперта о гораздо более сложных версиях перегрузки методов. Я бы попытался объяснить это, но он работает намного лучше, и я когда-либо мог: http://blogs.msdn.com/b/ericlippert/archive/2006/04/05/odious-ambiguous-overloads-part-one.aspx

из спецификации С# 4.0:

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

 class Test {   
      static void F() {
        Console.WriteLine("F()");   
      }     
      static void F(object x) {
        Console.WriteLine("F(object)");     
      }
      static void F(int x) {
        Console.WriteLine("F(int)");    
      }
      static void F(double x) {
        Console.WriteLine("F(double)");     
      }
      static void F<T>(T x) {
        Console.WriteLine("F<T>(T)");   
      }
      static void F(double x, double y) {
        Console.WriteLine("F(double,double)");  
      }     

      static void Main() {
        F();                // Invokes F()
        F(1);           // Invokes F(int)
        F(1.0);         // Invokes F(double)
        F("abc");       // Invokes F(object)
        F((double)1);       // Invokes F(double)
        F((object)1);       // Invokes F(object)
        F<int>(1);      // Invokes F<T>(T)
        F(1, 1);        // Invokes F(double, double)
      } 
}

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

person kemiller2002    schedule 25.05.2011
comment
К счастью, это особенно причудливый и нереалистичный пример проблемы с разрешением перегрузки (в ссылке, которую вы дали на мой блог). Надеюсь, вряд ли кто-нибудь столкнется с этим в реальной жизни. - person Eric Lippert; 25.05.2011
comment
Что мне действительно нравится в этой статье, так это то, что она показывает, как то, что кажется простым на первый взгляд, может оказаться чем-то ужасно сложным. На первый взгляд, разрешение перегрузок кажется простым, но на самом деле из-за большого количества случаев сделать это очень сложно. - person kemiller2002; 25.05.2011
comment
Действительно, разрешение перегрузок относится как минимум к классу сложности NP-HARD в C#. Вы можете (теоретически) заставить компилятор решить задачу коммивояжера, чтобы определить, какую перегрузку вызывать! - person Eric Lippert; 25.05.2011
comment
Танки! Я читал статью Erics, прежде чем публиковать, но нашел ее слишком сложной для моего простого случая... Думаю, мне следует пойти и получить копию спецификаций... - person Vegar; 26.05.2011

Как говорит Кевин, существует процесс разрешения перегрузок. Основная схема процесса такова:

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

Фильтры довольно сложные. Например, метод, изначально объявленный в более производном типе, всегда лучше, чем метод, изначально объявленный в менее производном типе. Метод, в котором типы аргументов точно соответствуют типам параметров, лучше, чем метод, в котором есть неточные совпадения. И так далее. Точные правила см. в спецификации.

В вашем конкретном примере алгоритм «лучшести» прост. Точное совпадение int с int лучше, чем неточное совпадение int с long.

person Eric Lippert    schedule 25.05.2011

Я бы сказал, если вы превысите нижний предел

-2,147,483,648 to 2,147,483,647

управление перейдет к long

Диапазон для long

–9,223,372,036,854,775,808 to 9,223,372,036,854,775,807

Максимальное значение для int

foo.bar(-2147483648);

or

foo.bar(2147483648);

Long получит управление, если мы превысим значение на 2147483648

person Pankaj    schedule 25.05.2011