Подтвердить что ты не робот

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

Рассмотрим следующий класс,

class Foo
{
    public Foo(int count)
    {
        /* .. */
    }

    public Foo(int count)
    {
        /* .. */
    }
}

Выше код недействителен и не будет компилироваться. Теперь рассмотрим следующий код,

class Foo<T>
{
    public Foo(int count)
    {
        /* .. */
    }

    public Foo(T t)
    {
        /* .. */
    }
}

static void Main(string[] args)
{
    Foo<int> foo = new Foo<int>(1);
}

Выше код действителен и хорошо компилируется. Он вызывает Foo (int count).

Мой вопрос: если первый недопустимый, как второй может быть действительным? Я знаю класс Foo <T> действительно, потому что T и int - разные типы. Но когда он используется как Foo <int> foo = new Foo <int> (1), T получает целочисленный тип, и оба конструктора будут иметь одну и ту же подпись справа? Почему компилятор не показывает ошибку, а не выбирает перегрузку для выполнения?

4b9b3361

Ответ 1

Ваш вопрос был горячо обсужден, когда разрабатывались С# 2.0 и система типового типа в CLR. Так горячо, на самом деле, что "связанная" спецификация С# 2.0, опубликованная A-W, фактически имеет неправильное правило в ней! Существует четыре возможности:

1) Сделать незаконным объявление общего класса, который может быть POSSIBLY неоднозначным в рамках НЕКОТОРЫХ конструкций. (Это неверно указывает связанная спецификация.) Таким образом, ваше объявление Foo<T> было бы незаконным.

2) Сделать незаконным создание универсального класса способом, который создает неоднозначность. объявление Foo<T> было бы законным, построение Foo<double> было бы законным, но построение Foo<int> было бы незаконным.

3) Сделайте все законным и используйте трюки с разрешением перегрузки, чтобы выяснить, лучше ли общая или неродственная версия. (Это то, что на самом деле делает С#.)

4) Сделайте что-то еще, о чем я не думал.

Правило № 1 - плохая идея, потому что это делает некоторые очень распространенные и безопасные сценарии невозможными. Рассмотрим, например:

class C<T>
{
  public C(T t) { ... } // construct a C that wraps a T
  public C(Stream state) { ... } // construct a C based on some serialized state from disk
}

Вы хотите, чтобы это было незаконным только потому, что C<Stream> неоднозначно? Тьфу. Правило № 1 - это плохая идея, поэтому мы ее отказали.

К сожалению, это не так просто. IIRC правила CLI говорят, что реализации разрешено отклонять как незаконные конструкции, которые фактически вызывают неоднозначность сигнатур. То есть правила CLI являются чем-то вроде правила №2, тогда как С# фактически реализует правило № 3. Это означает, что теоретически могут быть законные С# -программы, которые переводят на незаконный код, что очень печально.

Еще несколько мыслей о том, как эти разногласия делают наши жизни несчастными, вот несколько статей, которые я написал на эту тему:

http://blogs.msdn.com/ericlippert/archive/2006/04/05/569085.aspx

http://blogs.msdn.com/ericlippert/archive/2006/04/06/odious-ambiguous-overloads-part-two.aspx

Ответ 2

Нет никакой двусмысленности, потому что компилятор выберет наиболее определенную перегрузку Foo(...), которая соответствует. Поскольку метод с параметром общего типа считается менее конкретным, чем соответствующий не общий метод, Foo(T) поэтому менее специфичен, чем Foo(int), когда T == int. Соответственно, вы вызываете перегрузку Foo(int).

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

Ответ 3

Эрик Липперт blogged об этом недавно.

Ответ 4

Дело в том, что у них нет одинаковой сигнатуры - один использует дженерики, а другой - нет.

С помощью этих методов вы также можете вызвать его с помощью объекта non-int:

Foo<string> foo = new Foo<string>("Hello World");