Почему ограничения типа не являются частью сигнатуры метода?

ОБНОВЛЕНИЕ: Начиная с C# 7.3, это больше не должно быть проблемой. Из примечаний к выпуску:

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

До C# 7.3:

Итак, я прочитал Эрик Липперт "Ограничения не являются частью подписи", и теперь я понимаю, что спецификация указывает, что ограничения типов проверяются ПОСЛЕ разрешения перегрузки, но я до сих пор не понимаю, почему это ДОЛЖНО быть так. . Ниже приведен пример Эрика:

static void Foo<T>(T t) where T : Reptile { }
static void Foo(Animal animal) { }
static void Main() 
{ 
    Foo(new Giraffe()); 
}

Это не компилируется, потому что разрешение перегрузки для: Foo(new Giraffe()) предполагает, что Foo<Giraffe> является лучшим соответствием перегрузки, но тогда ограничения типа не работают, и выдается ошибка времени компиляции. По словам Эрика:

Принцип здесь заключается в разрешении перегрузки (и определении типа метода) в поиске наилучшего возможного соответствия между списком аргументов и списком формальных параметров каждого метода-кандидата. То есть они смотрят на сигнатуру метода-кандидата.

Ограничения типа НЕ являются частью подписи, но почему они не могут быть частью? В каких сценариях не рекомендуется рассматривать ограничения типов как часть подписи? Это сложно или невозможно реализовать? Я не выступаю за то, чтобы, если наилучшую выбранную перегрузку по какой-либо причине невозможно вызвать, тогда молча откатывайтесь ко второй лучшей; Я бы ненавидел это. Я просто пытаюсь понять, почему нельзя использовать ограничения типов, чтобы повлиять на выбор лучшей перегрузки.

Я предполагаю, что внутри компилятора C#, только для целей разрешения перегрузки (он не переписывает метод навсегда), следующее:

static void Foo<T>(T t) where T : Reptile { }

превращается в:

static void Foo(Reptile  t) { }

Почему вы не можете «втянуть» ограничения типа в список формальных параметров? Как это меняет подпись в плохом смысле? Я чувствую, что это только усиливает подпись. Тогда Foo<Reptile> никогда не будет рассматриваться как кандидат на перегрузку.

Изменить 2: неудивительно, что мой вопрос был таким запутанным. Я неправильно прочитал блог Эрика и привел неправильный пример. Я отредактировал пример, который считаю более подходящим. Я также изменил название, чтобы оно было более конкретным. Этот вопрос не кажется таким простым, как я сначала себе представлял, возможно, я упускаю какую-то важную концепцию. Я менее уверен, что это материал stackoverflow, возможно, лучше перенести этот вопрос/обсуждение в другое место.


person Daryl    schedule 25.02.2012    source источник
comment
Немного об игуанах, которое вы процитировали в начале своего вопроса, было предназначено для иллюстрации ситуации, когда вывод типа действительно учитывает ограничение, а именно ограничение на T в C‹T›, и, следовательно, разрешение перегрузки в конечном итоге выбирает метод неуниверсальный в этом примере. Вы уверены, что именно этот фрагмент статьи вы собираетесь процитировать, чтобы задать этот вопрос? Остальная часть вопроса, по-видимому, логически не следует из него.   -  person Eric Lippert    schedule 25.02.2012
comment
Я думаю, скорее, вы намеревались спросить, почему, когда вывод типа успешен, но выводится тип, который нарушает ограничение параметра типа метода, почему разрешение перегрузки не удается, когда есть является альтернативой. Я нахожу этот вопрос очень запутанным, но опять же, это запутанная часть спецификации.   -  person Eric Lippert    schedule 25.02.2012
comment
Ты прав. Я неправильно прочитал ваш блог и использовал неправильный пример. Неудивительно, что мой вопрос был таким запутанным. Я попытался прояснить свой вопрос как можно лучше; Я также собираюсь прочитать некоторые из ваших других сообщений в блоге, чтобы посмотреть, смогу ли я улучшить свое понимание предмета.   -  person Daryl    schedule 26.02.2012
comment
Я заметил, что запись в блоге Эрика содержит 9 страниц комментариев (blogs.msdn.com/b/ericlippert/archive/2009/12/10/), в рамках которого Эрик написал множество ответов на многие вопросы, подобные моему. Если у меня будет время, я попытаюсь сделать подборку соответствующих ответов Эрика.   -  person Daryl    schedule 26.02.2012


Ответы (2)


Компилятор C# не должен рассматривать ограничения типа как часть сигнатуры метода, поскольку они не являются частью сигнатуры метода для среды CLR. Было бы катастрофой, если бы разрешение перегрузки работало по-разному для разных языков (главным образом из-за динамического связывания, которое может происходить во время выполнения и не должно отличаться от одного языка к другому, иначе разразится ад).

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

person Falanwe    schedule 26.02.2012
comment
Но это просто смещает вопрос с того, почему он не является частью C#, на вопрос, почему он не является частью CLR... Существует принципиальная проблема с поддержкой разрешения перегрузки, которое учитывает ограничения типа (см. мой ответ). - person Eric J.; 26.02.2012
comment
В нескольких языках .Net есть несколько функций, которые не реализованы в других. Основная причина заключается в том, что вспомогательный синтаксис может быть тривиально преобразован в правильный синтаксис. Это один из самых простых способов просто определить, какой вызов метода следует разместить. Кроме того, это вряд ли будет единственным случаем возможной двусмысленности в языке, и у компилятора нет никаких причин просто не использовать то, что он находит, или выдавать ошибку, если он не может разрешить вызов конкретного метода. других ситуаций. Функции языка не являются функциями виртуальной машины. - person Tamir Daniely; 15.09.2015

Если T соответствует нескольким ограничениям, вы создаете неоднозначность, которая не может быть разрешена автоматически. Например, у вас есть один общий класс с ограничением

where T : IFirst

и еще один с ограничением

where T : ISecond

Теперь вы хотите, чтобы T был классом, который реализует как IFirst, так и ISecond.

Пример конкретного кода:

public interface IFirst
{
    void F();
}

public interface ISecond
{
    void S();
}

// Should the compiler pick this "overload"?
public class My<T> where T : IFirst
{
}

// Or this one?
public class My<T> where T : ISecond
{
}

public class Foo : IFirst, ISecond
{
    public void Bar()
    {
        My<Foo> myFoo = new My<Foo>();
    }

    public void F() { }
    public void S() { }
}
person Eric J.    schedule 25.02.2012
comment
.. Это вызывает конфликт имен; в моем вопросе ошибка вызвана тем, что компилятор не знает, какую перегрузку метода выбрать для вызова метода: Bar(new Iguana(), null). Я, наверное, медлительный, но пока не вижу связи между этими двумя. Я собираюсь изучить ваш пример немного больше .. - person Daryl; 25.02.2012
comment
@EricJ вы также можете вызвать неоднозначность метода без использования дженериков - та же ситуация может возникнуть, если две перегрузки метода принимают IFirst и ISecond соответственно, и вы пытаетесь решить, какой метод вызывать с аргументом типа Foo. Я не думаю, что именно поэтому команда C# решила не делать его частью сигнатуры метода. - person Chris Shain; 26.02.2012
comment
@ChrisShain Хм, но если у вас есть неоднозначность для вызова метода, принимающего IFirst и ISecond, компилятор выдаст ошибку, и вы можете решить проблему, приведя параметр к нужному интерфейсу. Если бы общие ограничения были частью сигнатур метода, вам понадобился бы синтаксис для выбора между различными ограничениями на сайте вызова, а любые изменения в общих ограничениях библиотеки означали бы, что вызывающий код должен быть перекомпилирован для продолжения работы, что я считаю менее чем идеальным. . - person Mike Marynowski; 09.09.2017