[Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 30.01.09 20:22
Оценка: 173 (12)
Скомпилируется ли этот код?

using System;

class Program
{
    static void Main()
    {
        Bar(T => T.Foo());
        Bar(S => S.Foo());
    }
    static void Bar(Action<T> x) { Console.WriteLine("T"); }
    static void Bar(Action<S> x) { Console.WriteLine("S"); }
}

class T
{
    public static void Foo() { }
}

class S
{
    public static void Foo() { }
}
Re: [Этюд, C#] Overload resolution
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 30.01.09 20:46
Оценка:
Здравствуйте, nikov, Вы писали:

N>Скомпилируется ли этот код?


Да, и выведет
Т
S
[КУ] оккупировала армия.
Re[2]: [Этюд, C#] Overload resolution
От: samius Япония http://sams-tricks.blogspot.com
Дата: 30.01.09 20:51
Оценка: :))) :)
Здравствуйте, koandrew, Вы писали:

K>Здравствуйте, nikov, Вы писали:


N>>Скомпилируется ли этот код?


K>Да, и выведет

K>Т
K>S

Можно подумать, что у nikov-а под рукой нет компилятора и он обращается к форумчанам за помощью в поиске ответа на вопрос. А чтобы никто не догадался, что это он с телефона запостил, приписывает [Этюд] в заголовок.

Может будут какие-то соображения, почему этот код компилируется, а главное, почему выводит T и S?
Re[3]: [Этюд, C#] Overload resolution
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 31.01.09 04:37
Оценка:
Здравствуйте, samius, Вы писали:

S>Можно подумать, что у nikov-а под рукой нет компилятора и он обращается к форумчанам за помощью в поиске ответа на вопрос. А чтобы никто не догадался, что это он с телефона запостил, приписывает [Этюд] в заголовок.


S>Может будут какие-то соображения, почему этот код компилируется, а главное, почему выводит T и S?


Не уверен точно, но второе видимо объясняется тем, что

Type inference occurs as part of the compile-time processing of a method invocation (§7.5.5.1) and takes place before the overload resolution step of the invocation.

(§7.4.2)
Компилируемость же видимо объясняется §7.5.4.1:

In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred. For example:
struct Color
{
public static readonly Color White = new Color(...);
public static readonly Color Black = new Color(...);
public Color Complement() {...}
}
class A
{
public Color Color; // Field Color of type Color
void F() {
Color = Color.Black; // References Color.Black static member
Color = Color.Complement(); // Invokes Complement() on Color field
}
static void G() {
Color c = Color.White; // References Color.White static member
}
}
Within the A class, those occurrences of the Color identifier that reference the Color type are underlined, and those that reference the Color field are not underlined.

P.S. Тем, кто осилил §7.4 (особенно §7.4.2) ИМХО надо памятник при жизни ставить — без ящика водки не разберёшься
[КУ] оккупировала армия.
Re[4]: [Этюд, C#] Overload resolution
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 31.01.09 05:39
Оценка:
Здравствуйте, koandrew, Вы писали:

K>Не уверен точно, но второе видимо объясняется тем, что

K>Type inference occurs as part of the compile-time processing of a method invocation (§7.5.5.1) and takes place before the overload resolution step of the invocation.

(§7.4.2)


Попробую пояснить, что я имею в виду. При выводе типа аргумента компилятор смотрит на использование методов объекта внутри лямбды. В нашем случае он видит использование метода типа T в первом случае и типа S во втором. Поскольку на этом этапе компиляции для него статические и экземплярные методы суть одно и то же, он выводит тип Action<T> для первой лямбды и Action<S> для второй. Дальше подключается разрешение перегрузок, и соответственно вызываются методы Bar, а также компилятор соображает, что в лямбдах речь идёт о статических методах, но это уже ничего не меняет, т.к. этап выведения типов уже завершён.

Надеюсь, я понятно изъяснил свои мысли...
[КУ] оккупировала армия.
Re[4]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 31.01.09 06:46
Оценка:
Здравствуйте, koandrew, Вы писали:

S>>Может будут какие-то соображения, почему этот код компилируется, а главное, почему выводит T и S?


K>Компилируемость же видимо объясняется §7.5.4.1:

K>P.S. Тем, кто осилил §7.4 (особенно §7.4.2) ИМХО надо памятник при жизни ставить — без ящика водки не разберёшься

Про 7.5.4.1 замечено верно. В остальном последовательность рассуждений немного не та. Type inference здесь совершенно не при чем.

Кстати, в спецификации для C# 4.0 раздел 7.4.2 заметно усложнится за счет появляния ковариантности и контравариантности и более гибких механизмов поиска подходящих типов.
Re[5]: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 31.01.09 07:45
Оценка:
Здравствуйте, nikov, Вы писали:

N>Кстати, в спецификации для C# 4.0 раздел 7.4.2 заметно усложнится за счет появляния ковариантности и контравариантности и более гибких механизмов поиска подходящих типов.


А когда будет доступна предварительная спецификация на C# 4.0?
Re[6]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 31.01.09 11:38
Оценка:
Здравствуйте, Алексей., Вы писали:

А>А когда будет доступна предварительная спецификация на C# 4.0?


Пока неизвестно.
Re: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.02.09 20:11
Оценка:
Здравствуйте, nikov, Вы писали:

N>Скомпилируется ли этот код?


Ну, ты бы вряд ли создал бы этюд по этому поводу если бы код скомпилировался бы.
И на мой взгляд — это натуральный баг.

Причем я пробовал применять два алгоритма и оба приводят к багу. Вот они:
1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo.
2. Компилятор понимает, что в T и S нужного члена нет и пытается вызвать статические члены принимая T и S за типы. При этом должна получиться неоднозначность лябды, так как оба вызова метода T.Foo и S.Foo никак не влияют на тип самой лябды.

Если ты считаешь, что это не баг, то поясни логику используемую компилятором Шарпа.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 02.02.09 21:26
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Ну, ты бы вряд ли создал бы этюд по этому поводу если бы код скомпилировался бы.

Я не понял, он у тебя не компилируется что ли?

VD>И на мой взгляд — это натуральный баг.

Нет, здесь нет бага. Но объясню попозже. Может быть, кто-то все-таки сам разберется.
Re[2]: [Этюд, C#] Overload resolution
От: Блудов Павел Россия  
Дата: 03.02.09 02:27
Оценка: 2 (1)
Здравствуйте, VladD2, Вы писали:

VD>1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo.

Фишка в том, что это не имена. Что интересно, РеШарпер 4.5 предлагает заменить их на
Bar(t => T.Foo());
Bar(s => S.Foo());

После чего радостно заявляет что вызов стал неоднозначным.
Вот так, кстати, тоже можно:
using System;
using System.Xml;

class Program
{
    static void Main()
    {
        Bar(XmlWriter => XmlWriter.Create(string.Empty));
        Bar(XmlReader => XmlReader.Create(string.Empty));
    }
    static void Bar(Action<XmlWriter> x) { Console.WriteLine("W"); }
    static void Bar(Action<XmlReader> x) { Console.WriteLine("R"); }
}

А вот так нельзя:
using System;
using System.Xml;

class Program
{
    static void Main()
    {
        Bar(XmlWriter => XmlWriter.Flush());
        Bar(XmlReader => XmlReader.Flush());
    }
    static void Bar(Action<XmlWriter> x) { Console.WriteLine("W"); }
    static void Bar(Action<XmlReader> x) { Console.WriteLine("R"); }
}

Точнее, тоже можно, компилятор c# это компилирует без вопросов, но результат будет неожиданный
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Re[3]: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 03.02.09 07:56
Оценка:
Здравствуйте, nikov, Вы писали:

N>Я не понял, он у тебя не компилируется что ли?


Vlad2 его наверное на Nemerle проверял
Re: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 03.02.09 09:10
Оценка:
Здравствуйте, nikov, Вы писали:

Попробую сделать предположения:
1. Какой из вариантов T или S определяются перебором. Сначала в лямбда-выражение подставляется вариант с типом T, потом вариант с типом S. Тот из вариантов который успешно скомпилировался тот и считается подходящим.
2. Согласно пункту 7.5.4.1 используется статический вызов метода Foo(), а не вызов через параметр.
Re: [Этюд, C#] Overload resolution
От: Пельмешко Россия blog
Дата: 03.02.09 10:56
Оценка:
Здравствуйте, nikov, Вы писали:
    static void Main()
    {
        Bar(T => T.Foo());
        Bar(S => S.Foo());
    }

Думаю дело в записи лямбды
Запись выше — всего сокращение от этой, полученная путём опускания имени неиспользуемого параметра:
Bar( (T t) => T.Foo() );
Bar( (S s) => S.Foo() );

Аналогично:
Bar(delegate( T t ) { T.Foo(); });
Bar(delegate( S s ) { S.Foo(); });

В release генерируемый MSIL этих лямбд эквивалентен.

Записанные в таком виде лямбды чётко подходят одна только под Action<T>, другая только под Action<S> исходя из декларации:
public delegate void Action<T>(T obj)


Но тогда ничего не пойму с:
class Program
{
    static void Main()
    {
        Bar(XmlWriter => XmlWriter.Flush());
        Bar(XmlReader => XmlReader.Flush());
    }
    static void Bar(Action<XmlWriter> x) { Console.WriteLine("W"); }
    static void Bar(Action<XmlReader> x) { Console.WriteLine("R"); }
}

Сгенрилось два метода, получающих XmlWriter, второй Bar() ни разу не использован..
Даже в тултипе над XmlReader.Flush() написано "(parameter) XmlWriter XmlReader"
Re[2]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 03.02.09 11:19
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Думаю дело в записи лямбды

П>Запись выше — всего сокращение от этой, полученная путём опускания имени неиспользуемого параметра:

Не-не-не, так не бывает. В лямбде можно опускать типы параметров, но не их имена (см. 7.14 Anonymous function expressions).
Re[2]: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 03.02.09 11:47
Оценка:
В спецификации так до сих пор четко и не сформулировано что же такое valid expression и valid block в контексте преобразования неявно типизированной анонимной функции к делегату (раздел 6.5).

Правильно ли я понимаю что это просто "компиляция" в контексте? Если выражение или блок успешно "скомпилировались" то выражение считается валидным, если неуспешно — невалидным?
Re[3]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 03.02.09 12:03
Оценка: 6 (1)
Здравствуйте, Алексей., Вы писали:

А>В спецификации так до сих пор четко и не сформулировано что же такое valid expression и valid block в контексте преобразования неявно типизированной анонимной функции к делегату (раздел 6.5).

Да, к сожалению, нет ясного определения.

А>Правильно ли я понимаю что это просто "компиляция" в контексте? Если выражение или блок успешно "скомпилировались" то выражение считается валидным, если неуспешно — невалидным?

Почти так. На самом деле некоторые ошибки компиляции игнорируются при определении валидности:

1) "Плохие" expression trees (содержащие элементы, непредставимые в текущей объектной модели)
2) Проблемы, связанные с reachability и definite assignment (как ни странно, в некоторых случаях они тоже зависят от типов параметров лямбды)
3) Ошибки связанные с использованием unsafe кода в итераторах
4) Предупреждения, превратившиеся в ошибки из-за опции Treat warnings as errors.
5) Проблемы с метками и goto

Этот список появился в результате большого количества экспериментов и распросов разработчика, который это реализовывал. К сожалению, это сообщение — это пока единственное место, где он приведен. И нет гарантий, что я что-то не упустил из виду.
Re[3]: [Этюд, C#] Overload resolution
От: Andir Россия
Дата: 03.02.09 13:52
Оценка:
Здравствуйте, Блудов Павел, Вы писали:

БП>Здравствуйте, VladD2, Вы писали:


VD>>1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo.

БП>Фишка в том, что это не имена.

Всё таки это имена, иначе вот это было бы неверно и не должно было бы компилироваться:
static void Main()
{
    Bar(T => { T.GetType(); T.Foo(); } );
    Bar(S => { S.GetType(); S.Foo(); } );
}


С Уважением, Andir!
using( RSDN@Home 1.2.0 alpha 4 rev. 1135 ) { /* Работаем */ }
Re[2]: [Этюд, C#] Overload resolution
От: Блудов Павел Россия  
Дата: 04.02.09 02:11
Оценка:
Здравствуйте, Пельмешко, Вы писали:

П>Но тогда ничего не пойму с:

П>
П>class Program
П>{
П>    static void Main()
П>    {
П>        Bar(XmlWriter => XmlWriter.Flush());
П>        Bar(XmlReader => XmlReader.Flush());
П>    }
П>    static void Bar(Action<XmlWriter> x) { Console.WriteLine("W"); }
П>    static void Bar(Action<XmlReader> x) { Console.WriteLine("R"); }
П>}
П>


Тут фишка в том, что у XmlReader нет метода Flush, ибо незачем.

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

В итоге мы потенциально имеем целый ряд проблем, связанный с разного рода "наведёнками".
В приведённом выше примере добавление статического метода Flush в клаcc XmlReader или добавление локального класса XmlReader приведёт к вызову второго Bar вместо первого. Плохо это не само по себе, а потому, что компилятор делает это молча. Отловить такие изменения в поведении можно только при 100% покрытии всех методов тестами. А такой подход к разработке ПО неконкурентноспособен.
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Re[4]: [Этюд, C#] Overload resolution
От: Блудов Павел Россия  
Дата: 04.02.09 02:22
Оценка:
Здравствуйте, Andir, Вы писали:

A>Всё таки это имена, иначе вот это было бы неверно и не должно было бы компилироваться:

A>
A>static void Main()
A>{
A>    Bar(T => { T.GetType(); T.Foo(); } );
A>    Bar(S => { S.GetType(); S.Foo(); } );
A>}
A>


Тогда и это должно компилироваться, но не делает этого:
static void Main()
{
    Bar(t => { t.GetType(); T.Foo(); } );
    Bar(s => { s.GetType(); S.Foo(); } );
}

Впрочем, я неверное неудачно выразился. "Это более чем имена" — так пойдёт? Компилятор почему-то трактует данный код как
Bar((T T) => { T.GetType(); T.Foo(); } );
Bar((S S) => { S.GetType(); S.Foo(); } );

Почему он так делает Возможно это своего рода оптимизация, позволяющая сократить время на перебор вариантов.
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Re[5]: [Этюд, C#] Overload resolution
От: Andir Россия
Дата: 04.02.09 03:06
Оценка: 43 (1)
Здравствуйте, Блудов Павел, Вы писали:
A>>Всё таки это имена, иначе вот это было бы неверно и не должно было бы компилироваться:
[code skipped]
БП>Тогда и это должно компилироваться, но не делает этого:
БП>
БП>static void Main()
БП>{
БП>    Bar(t => { t.GetType(); T.Foo(); } );
БП>    Bar(s => { s.GetType(); S.Foo(); } );
БП>}
БП>


Да, конечно, если разрешить неоднозначность (например указав тип переменной), то будет компилироваться. А в предыдущем случае, компилятор каким-то своим умом разрешает эту неоднозначность ("ум" в этом случае активный, а не статично описанный похоже).

БП>Впрочем, я неверное неудачно выразился. "Это более чем имена" — так пойдёт? Компилятор почему-то трактует данный код как

БП>
БП>Bar((T T) => { T.GetType(); T.Foo(); } );
БП>Bar((S S) => { S.GetType(); S.Foo(); } );
БП>

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

Не-е, я думаю это не трактовка имени переменной, а вывод типа так срабатывает. То есть вариант выбора типа, в данном случае, возможен и он единственный, точно также как:

Так можно:
static void Main()
{
    var T = default(T);
    T.Foo(); // ОК
}

А так нельзя:
static void Main()
{
    var t = default(T);
    t.Foo(); // error CS0176: Member 'T.Foo()' cannot be accessed with an instance reference; qualify it with a type name instead
}

И так нельзя:
static void Main()
{
    var T = default(S);
    T.Foo(); // error CS0176: Member 'S.Foo()' cannot be accessed with an instance reference; qualify it with a type name instead
}


С Уважением, Andir!
Re[3]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 04:58
Оценка:
Здравствуйте, nikov, Вы писали:

VD>>Ну, ты бы вряд ли создал бы этюд по этому поводу если бы код скомпилировался бы.

N>Я не понял, он у тебя не компилируется что ли?

Компилируется. Это так называемый сарказм.

VD>>И на мой взгляд — это натуральный баг.

N>Нет, здесь нет бага. Но объясню попозже. Может быть, кто-то все-таки сам разберется.

По-моему время уже пришло.

ЗЫ

На мой взгляд баг чистой воды. Не в компиляторе, так в стандарте.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:01
Оценка:
Здравствуйте, Блудов Павел, Вы писали:

VD>>1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo.

БП>Фишка в том, что это не имена. Что интересно, РеШарпер 4.5 предлагает заменить их на

А что же?

И на основании чего вдруг в одном случае это имя параметра, а в другом типа?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:08
Оценка: 1 (1)
Здравствуйте, Блудов Павел, Вы писали:

A>>Всё таки это имена, иначе вот это было бы неверно и не должно было бы компилироваться:

A>>
A>>static void Main()
A>>{
A>>    Bar(T => { T.GetType(); T.Foo(); } );
A>>    Bar(S => { S.GetType(); S.Foo(); } );
A>>}
A>>


БП>Тогда и это должно компилироваться, но не делает этого:

БП>
БП>static void Main()
БП>{
БП>    Bar(t => { t.GetType(); T.Foo(); } );
БП>    Bar(s => { s.GetType(); S.Foo(); } );
БП>}
БП>


А оно и скомпилируется если устранить неоднозначность:
using System;

class Program
{
    static void Main()
    {
        //Bar(T => { T.GetType(); T.Foo(); });
        //Bar(S => { S.GetType(); S.Foo(); });
        Bar(new Action<S>(t => { t.GetType(); T.Foo(); }));
        Bar(new Action<S>(s => { s.GetType(); S.Foo(); }));
    }
    static void Bar(Action<T> x) { Console.WriteLine("T"); }
    static void Bar(Action<S> x) { Console.WriteLine("S"); }
}

class T
{
    public static void Foo() { }
}

class S
{
    public static void Foo() { }
}


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

БП>Впрочем, я неверное неудачно выразился. "Это более чем имена" — так пойдёт? \


Не, не пойдет. Более чем — это уже магия. А я, знаете ли, верю в фокусы, но не в магию.

БП>Компилятор почему-то трактует данный код как

БП>
БП>Bar((T T) => { T.GetType(); T.Foo(); } );
БП>Bar((S S) => { S.GetType(); S.Foo(); } );
БП>

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

А с какого рожна делаются такие макроподстановки? Компилятор телепат?

Самое разумное в данном случае было бы сообщить об ошибке, чтобы человек принял меры. А так имеется замазанная проблема которая когда-то обязательно выстрелит.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:12
Оценка:
Здравствуйте, Andir, Вы писали:

A>Не-е, я думаю это не трактовка имени переменной, а вывод типа так срабатывает. То есть вариант выбора типа, в данном случае, возможен и он единственный, точно также как:


A>Так можно:

A>
A>static void Main()
A>{
A>    var T = default(T);
A>    T.Foo(); // ОК
A>}
A>


Ага. Забавно.

Еще забавно, что вот такой код:
Bar(S => { S.GetType(); T.Foo(); });

выдает сообщение об ошибке:

error CS0121: The call is ambiguous between the following methods or properties: 'Program.Bar(System.Action<T>)' and 'Program.Bar(System.Action<S>)'

Так что это явно странная трактовка имени переменной.

На мой взгляд — это баг чистой воды. По крайне мере поступать так не логично, а значит вредно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:18
Оценка:
Здравствуйте, Блудов Павел, Вы писали:

БП>А проблема, обсуждаемая в этой теме, как я понял, в том, что компилятор нарушает принцип "либо дудочка, либо кувшинчик".

БП>Причём из самых благих побуждений — он пытается максимально угодить потребителям.

Это банальный баг в компиляторе. Вывод типа параметра на основании его имени, имеющий здесь место — это бред сивой кобылы.

Приведенный Андиром пример четко демонстрирует ошибку.

В общем, на мой взгляд Никову давно пора перестать интриговать и объяснить свою позицию. Ведь он считает, что бага нет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:23
Оценка: 1 (1)
Здравствуйте, koandrew, Вы писали:

S>>Может будут какие-то соображения, почему этот код компилируется, а главное, почему выводит T и S?


K>Не уверен точно, но второе видимо объясняется тем, что

K>Type inference occurs as part of the compile-time processing of a method invocation (§7.5.5.1) and takes place before the overload resolution step of the invocation.

(§7.4.2)


Здорово. И как компилятор выводит, что у параметра с именем T тип T? Это не вывод типов, а телепатия.
По фигу когда делается вывод типов. Важно то, что он делается не верно.

Вот тебе вариации на эту тему:
Bar(S => { S.GetType(); T.Foo(); }); // error CS0121: The call is ambiguous between the following methods or properties: 'Program.Bar(System.Action<T>)' and 'Program.Bar(System.Action<S>)'
Bar(new Action<S>(T => { T.GetType(); T.Foo(); })); // error CS0176: Member 'S.Foo()' cannot be accessed with an instance reference; qualify it with a type name instead
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:27
Оценка: +1
Здравствуйте, koandrew, Вы писали:

K>Попробую пояснить, что я имею в виду. При выводе типа аргумента компилятор смотрит на использование методов объекта внутри лямбды. В нашем случае он видит использование метода типа T в первом случае и типа S во втором. Поскольку на этом этапе компиляции для него статические и экземплярные методы суть одно и то же, он выводит тип Action<T> для первой лямбды и Action<S> для второй. Дальше подключается разрешение перегрузок, и соответственно вызываются методы Bar, а также компилятор соображает, что в лямбдах речь идёт о статических методах, но это уже ничего не меняет, т.к. этап выведения типов уже завершён.


K>Надеюсь, я понятно изъяснил свои мысли...


Понятно только одно. Ты вообще не понял, что тут происходит.
Попробуй обдумать простую мысль. T и S в приведенном коде — это не типы, а имена параметров лябд. Таким образом обращение к ним через точку должно приводить к поиску экзеплярных методов с именем Foo. Но таковых нет!
Компилятор просто не имеет право делать предположений о типах параметров. Он или должен вывести их из инициализации, или сообщить о неоднозначности.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:36
Оценка: +1
Здравствуйте, nikov, Вы писали:

N>Про 7.5.4.1 замечено верно.


ОК.
Все что сказано в 7.5.4.1 — это то, что если свойство, параметр и т.п. перкрывают тип, то все равно можно обратиться к его статическим членам через имя типа.
Но на каком основании делается вывод о том, что у параметра T тип T, а у параметра S тип S?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: [Этюд, C#] Overload resolution
От: Sinix  
Дата: 04.02.09 06:07
Оценка:
Здравствуйте, nikov, Вы писали:

N>Скомпилируется ли этот код?


N>
N>using System;

N>class Program
N>{
N>    static void Main()
N>    {
N>        Bar(T => T.Foo());
N>        Bar(S => S.Foo());
N>    }
N>    static void Bar(Action<T> x) { Console.WriteLine("T"); }
N>    static void Bar(Action<S> x) { Console.WriteLine("S"); }
N>}

N>class T
N>{
N>    public static void Foo() { }
N>}

N>class S
N>{
N>    public static void Foo() { }
N>}
N>



Не должон — компилятору неоткуда выгрести тип переменной ибо вызывается неизвестно что. Насколько помню синтаксис, static метод не может дёргаться через инстанс. Вот если бы класс был бы статик — тогда точно нет.

Что упустил?
Re: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 04.02.09 08:05
Оценка: 69 (4) +1 -1
Здравствуйте, nikov, Вы писали:

Пришло время подытожить почему данный код компилируется и является корректным с точки зрения спецификации.
Т.к. случаи Bar(T => T.Foo()); и Bar(S => S.Foo()); аналогичны достаточно рассмотреть только первый случай.

Для вызова функции Bar(T => T.Foo()); производится поиск вызываемого метода.
Находятся два кандидата static void Bar(Action<T> x) и static void Bar(Action<S> x)

Далее, согласно алгоритму overload resolution производится проверка какой из двух методов является подходящим (applicable). Для этого делается проверка может ли лямбда T => T.Foo() быть преобразована к делегату Action<T> и Action<S> соответственно (раздел 6.5). Проверка осуществляется как "условная компиляция" выражения T => T.Foo() в заданом контексте.

Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T. "Условная компиляция" происходит без ошибок. Соответственно T => T.Foo() является valid expression в данном контексте.

Преобразование лямбды к типу Action<S> недопустимо. Для переменной T выводится что она имеет тип S. Вызов T.Foo() ошибочен, т.к. вызов статического метода недопустим через экземпляр класса. Соответственно T => T.Foo() не является valid expression в данном контексте.
Re[2]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 04.02.09 08:47
Оценка: -1
Здравствуйте, Алексей., Вы писали:

А>Пришло время подытожить почему данный код компилируется и является корректным с точки зрения спецификации.

А>[skipped]

Все абсолютно верно!
Re[6]: [Этюд, C#] Overload resolution
От: Блудов Павел Россия  
Дата: 04.02.09 10:03
Оценка:
Здравствуйте, Andir, Вы писали:
A>Не-е, я думаю это не трактовка имени переменной, а вывод типа так срабатывает.

Точно.
static void Main()
{
    Bar((T T) => { T.GetType(); T.Foo(); });
    Bar((T S) => { S.GetType(); S.Foo(); });
    Bar((S T) => { T.GetType(); T.Foo(); });
    Bar((S S) => { S.GetType(); S.Foo(); });
}

Два средних компилироваться отказываются.
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Re[2]: [Этюд, C#] Overload resolution
От: Mr.Cat  
Дата: 04.02.09 10:50
Оценка: +1
Здравствуйте, Алексей., Вы писали:

А>Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T. "Условная компиляция" происходит без ошибок. Соответственно T => T.Foo() является valid expression в данном контексте.


А почему этого не происходит во втором случае?

А>Преобразование лямбды к типу Action<S> недопустимо. Для переменной T выводится что она имеет тип S. Вызов T.Foo() ошибочен, т.к. вызов статического метода недопустим через экземпляр класса. Соответственно T => T.Foo() не является valid expression в данном контексте.
Re[3]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 04.02.09 11:30
Оценка: 6 (1)
Здравствуйте, Mr.Cat, Вы писали:

MC>Здравствуйте, Алексей., Вы писали:


А>>Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T. "Условная компиляция" происходит без ошибок. Соответственно T => T.Foo() является valid expression в данном контексте.


MC>А почему этого не происходит во втором случае?


А ты посмотрел в 7.5.4.1?

7.5.4.1 Identical simple names and type names
In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.


Во втором случае имя параметра и типа несогласованы, поэтому доступ к статическому члену недопустим, что приводит к отсутствию неявного преобразования от лямбды к делегату.
Re[4]: [Этюд, C#] Overload resolution
От: Mr.Cat  
Дата: 04.02.09 11:48
Оценка:
Здравствуйте, nikov, Вы писали:
N>Во втором случае имя параметра и типа несогласованы, поэтому доступ к статическому члену недопустим, что приводит к отсутствию неявного преобразования от лямбды к делегату.

Спасибо, не сразу заметил, что выделенное важно.
Re[3]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 04.02.09 12:03
Оценка: 33 (3)
Здравствуйте, Алексей., Вы писали:

А>Правильно ли я понимаю что это просто "компиляция" в контексте? Если выражение или блок успешно "скомпилировались" то выражение считается валидным, если неуспешно — невалидным?


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

using System;

class Program
{
    static void Main()
    {
        Foo(a => Foo(b => Foo(c => Foo(d => Foo(e => Foo(f => Foo(g => Foo(h => a))))))));
    }

    static string Foo(Func<byte, byte> f) { return null; }
    static string Foo(Func<short, short> f) { return null; }
    static string Foo(Func<int, int> f) { return null; }
    static string Foo(Func<long, long> f) { return null; }
    static string Foo(Func<string, string> f) { return null; }
}
Re[4]: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 04.02.09 12:10
Оценка:
Здравствуйте, nikov, Вы писали:

Угу, где-то в блоках разработчиков говорилось о том что вывод типов является NP-полной задачей. За пример, спасибо, не думал что так просто отправить компилятор в спячку.
Re[2]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.02.09 05:50
Оценка:
Здравствуйте, Алексей., Вы писали:

А>Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T.


Это логическая ошибка. В указаном пункте нигде не сказано, что допускается интерпретировать имя переменной как ее тип.
Что же сказано в этом пункте? А вот что:

In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.

Подчеркиваю! Тип Е должен быть таким же как тип Е. Другими словами мы должны иметь переменную или свойство имя которой совпадает с именем ее типа. Только при этом условии этот пункт должен срабатывать.
Сделано это чтобы имена переменных и свойств совпадающие с именами типов не скрывали статических членов этого типа.

В данном же случае имеет место неверное предположение о том, что тип параметра с именх Е тоже Е. Но это не верно! Это просто не откуда взять.

Так что компилятор Шарпа ведет себя не в соответствии с этим пунктом, а иначе. Он просто считает, что раз мы можем типизировать тело считая тип параметра равным Е, то значит все ОК. Но это не верное предположение. Программист мог попросту ошибиться.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.02.09 05:53
Оценка: +2
Здравствуйте, Mr.Cat, Вы писали:

MC>Здравствуйте, Алексей., Вы писали:


А>>Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T. "Условная компиляция" происходит без ошибок. Соответственно T => T.Foo() является valid expression в данном контексте.


MC>А почему этого не происходит во втором случае?


Потому-что это не верные выводы. На самом деле T и S всегда интерпретируются как типы. Только в первом случае тип скрыт одноименной переменной. Далее на основании ошибочной интерпретации пункта 7.5.4.1 делается предположение, что параметр T имеет тип Т, что в корне не верно.

В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.02.09 05:59
Оценка: +1
Здравствуйте, nikov, Вы писали:

N>А ты посмотрел в 7.5.4.1?


N>

N>7.5.4.1 Identical simple names and type names
N>In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.


И что ты узрел в выделенном?

Где ты видишь в данном примере, что у параметра тип совпадает с именем? Его тип не определен! Его просто не откуда вывести! То что кто-то там пытается из этого имени вызвать что-то еще ничего не значит.
Вот тебе простой пример демонстрирующий что будет если тип переменной выведется по другому:
Bar(new Action<S>(T => { T.GetType(); T.Foo(); })); // error CS0176: Member 'S.Foo()' cannot be accessed with an instance reference; qualify it with a type name instead

Что у нас изменилось? А только лишь тип параметра с именем "T".
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: [Этюд, C#] Overload resolution
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 05.02.09 06:18
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Понятно только одно. Ты вообще не понял, что тут происходит.

VD>Попробуй обдумать простую мысль. T и S в приведенном коде — это не типы, а имена параметров лябд. Таким образом обращение к ним через точку должно приводить к поиску экзеплярных методов с именем Foo. Но таковых нет!
VD>Компилятор просто не имеет право делать предположений о типах параметров. Он или должен вывести их из инициализации, или сообщить о неоднозначности.

Я сам нашёл опровержения моей теории, но поскольку nikov уже прокомментировал мой пост, не посчитал нужным что-то постить дополнительно...
[КУ] оккупировала армия.
Re[5]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.02.09 06:47
Оценка:
Здравствуйте, Алексей., Вы писали:

А>Угу, где-то в блоках разработчиков говорилось о том что вывод типов является NP-полной задачей.


Это шарпа то? Гы-гы. Ты видимо читал блоги каких-то других программистов. В Шарпе линейный вывод типов (только из инициализации).

Вот при выводе типов из использования (как в Немерле) разрешение перегрузок и правда является NP-полной задачей. Но и там можно применять эвристики делающие процесс разрешения перегрузок приемлемо бысрым.

А>За пример, спасибо, не думал что так просто отправить компилятор в спячку.


Это потому, что в компиляторы такие алгоритмы используются. Тот же пример на Немерле компилируется в мгновение ока. И интеграция на нем работает отлично:
using System;

module Program
{
    Main() : void
    {
      _ = Foo(a => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => a))))))));
    }

    Foo(_ : Func[byte, byte]) : string { null }
    Foo(_ : Func[short, short]) : string { null }
    Foo(_ : Func[int, int]) : string { null }
    Foo(_ : Func[long, long]) : string { null }
    Foo(_ : Func[string, string]) : string { null }
}

Если подвести мышку к параметру "a", то молниеносно выведится "(function parametr) a : string".
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 05.02.09 07:55
Оценка: 26 (1) +1 -1
Здравствуйте, VladD2, Вы писали:

VD>Это логическая ошибка. В указаном пункте нигде не сказано, что допускается интерпретировать имя переменной как ее тип.

7.5.4.1 так прямо и говорит выражение simple-name (в данном случае T), которое по сути всегда является идентификатором, в определенном случае может интерпретироваться как имя типа.

In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.


VD>В данном же случае имеет место неверное предположение о том, что тип параметра с именх Е тоже Е. Но это не верно! Это просто не откуда взять.


Виноват, забыл подробно описать как же компилятор "выводит" что параметр лямбды T имеет тип T.
Цитирую раздел 6.5:

If D has a void return type and the body of F is an expression, when each parameter of F is given the type of the corresponding parameter in D, the body of F is a valid expression (wrt §7) that would be permitted as a statement-expression (§8.6).


Рассмотрим преобразование лямбды T => T.Foo() в делегат Action<T>.

Action определен как public delegate void Action<T>(T obj);

Вызов инстанцированного делегата будет выглядеть как: void (T obj), где T это имя класса T в примере nikov'а, а не тип-параметр делегата.
Декларацию лямбды можно записать как: void (? T)

Сопоставляя параметры делегата и параметры лямбды получаем что параметр T лямбды имеет тип T.
Re[6]: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 05.02.09 08:12
Оценка: +1
Здравствуйте, VladD2, Вы писали:

VD>Это шарпа то? Гы-гы. Ты видимо читал блоги каких-то других программистов. В Шарпе линейный вывод типов (только из инициализации).


Я так понимаю реализация 7.4.2.11 Inferred return type порождает комбинаторику аналогичную приведенную nikov'ым примером.

VD>Вот при выводе типов из использования (как в Немерле) разрешение перегрузок и правда является NP-полной задачей. Но и там можно применять эвристики делающие процесс разрешения перегрузок приемлемо бысрым.


Это значит что найти пример который отправит в спячку Nemerle несколько сложнее.
Re[5]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 05.02.09 09:11
Оценка: 2 (1)
Здравствуйте, VladD2, Вы писали:

VD>Где ты видишь в данном примере, что у параметра тип совпадает с именем? Его тип не определен! Его просто не откуда вывести! То что кто-то там пытается из этого имени вызвать что-то еще ничего не значит.


Посмотри 6.5 Anonymous function conversions — там описано, как происходит преобразование от анонимной функции к делегатам, и откуда берутся типы параметров анонимной функции, если они не указаны явно.

An anonymous-method-expression or lambda-expression is classified as an anonymous function (§7.14). The expression does not have a type but can be implicitly converted to a compatible delegate type or expression tree type. Specifically, a delegate type D is compatible with an anonymous function F provided:
...
• If D has a void return type and the body of F is an expression, when each parameter of F is given the type of the corresponding parameter in D, the body of F is a valid expression (wrt §7) that would be permitted as a statement-expression (§8.6).


Как видишь, компилятору приходится заглядывать в тело анонимного метода, чтобы проверить, существует ли преобразование к делегату. Теперь, что происходит при обработке вызова метода Bar? Смотрим дальше:

7.5.5.1 Method invocations
• The set of candidate methods for the method invocation is constructed. For each method F associated with the method group M:
...
• F is applicable with respect to A (§7.4.3.1).

7.4.3.1 Applicable function member
A function member is said to be an applicable function member with respect to an argument list A when all of the following are true:
...
an implicit conversion (§6.1) exists from the argument to the type of the corresponding parameter


То есть, когда у тебя анонимный метод задан в качестве аргумента к перегруженному методу Bar, то компилятор перебирает методы и ищет те, к для которым существует неявное преобразование от анонимного метода к соответсвующему типу в сигнатуре. Поэтому производится несколько попыток задать типы (взятые из параметров делегата в сигнатуре) параметрам анонимного метода, и проверить, будет ли тело анонимного метода корректным. Те попытки, которые будут успешными, дадут кандидатов для overload resolution. В моем примере одна успешная попытка, там что overload resolution тривиален. Так что, тип есть откуда вывести.
Re[4]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 05.02.09 11:43
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее.


Только все происходило наоборот: я прочитал спецификацию, понял, что она приводит к таком интересному эффекту, а только потом написал код и убедился, что он работает, как предписано спецификацией. Никаких ошибок нет.
Re[6]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 05.02.09 11:48
Оценка: -1
Здравствуйте, VladD2, Вы писали:

А>>Угу, где-то в блоках разработчиков говорилось о том что вывод типов является NP-полной задачей.


VD>Это шарпа то? Гы-гы. Ты видимо читал блоги каких-то других программистов. В Шарпе линейный вывод типов (только из инициализации).


Как бы не так. Вывод из инициализации — это только для переменных, объявленных через var. Для параметров в лямбдах тип может выводиться из анализа использования этих параметров (куда они передаются, какие методы вызываются, какие операторы применяются, к чему они кастятся и т.д.). В этом случае строится дерево всевозможных вариантов и производится перебор. Я достаточно хорошо разбираюсь в этом процессе, и много обсуждал его и с его разработчиками в Mirosoft, и с теми, кто его реализовывал для ReSharper в JetBrains.
Re[6]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.02.09 15:20
Оценка:
Здравствуйте, nikov, Вы писали:

N> скипнуто...


Я не знаю зачем ты мне все это излагал. Все это я и так знал.

N>То есть, когда у тебя анонимный метод задан в качестве аргумента к перегруженному методу Bar, то компилятор перебирает методы и ищет те, к для которым существует неявное преобразование от анонимного метода к соответсвующему типу в сигнатуре. Поэтому производится несколько попыток задать типы (взятые из параметров делегата в сигнатуре) параметрам анонимного метода, и проверить, будет ли тело анонимного метода корректным. Те попытки, которые будут успешными, дадут кандидатов для overload resolution. В моем примере одна успешная попытка, там что overload resolution тривиален. Так что, тип есть откуда вывести.


У тебя будет два подходящих кандидата. А поведение компилятора C# ошибка.
Повторение одних и тех же аргументов не делают эти аргументы правильнее.

Ты лучше объясни почему ты считаешь, что 7.5.4.1 должен накладывать какие-то ограничения на тип параметра?

Там же ясно сказано:

In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred. For example:


Так что же тебе дает основание пологать, что параметр T имеет тот же тип что и тип T?

ЗЫ

Ты по этому поводу с разработчиками компилятора говорил?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.02.09 15:22
Оценка:
Здравствуйте, nikov, Вы писали:

N>Здравствуйте, VladD2, Вы писали:


VD>>В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее.


N>Только все происходило наоборот: я прочитал спецификацию, понял, что она приводит к таком интересному эффекту, а только потом написал код и убедился, что он работает, как предписано спецификацией. Никаких ошибок нет.


Я тебе уже 5 раз указал на ошибку в твоих рассуждениях. То что она совпала с работой компилятора может и случайность.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 05.02.09 16:17
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Ты лучше объясни почему ты считаешь, что 7.5.4.1 должен накладывать какие-то ограничения на тип параметра?

Потому что из 7.5.4.1 следует, что T.Foo() — это некорректное выражение, если T имеет тип, отличный от T, и корректное, если Т имеет тип Т.

VD>Там же ясно сказано:

VD>

VD>In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred. For example:


VD>Так что же тебе дает основание пологать, что параметр T имеет тот же тип что и тип T?

1) Во время overload resolution, такое основание мне дает то, что в данный момент рассматривается overload с типом Action<T> в сигнатуре.
2) После overload resolution, такое основание мне дает то, что во время overload resolution был выбран overload с типом Action<T> в сигнатуре.

VD>ЗЫ

VD>Ты по этому поводу с разработчиками компилятора говорил?

Говорил.
Re[6]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 05.02.09 16:20
Оценка: :))) :)
Здравствуйте, VladD2, Вы писали:

VD>Я тебе уже 5 раз указал на ошибку в твоих рассуждениях. То что она совпала с работой компилятора может и случайность.


Когда я говорю про overload resolution в C#, у меня не бывает ошибок
Я тебе уже несколько раз объяснил, почему то, что ты считаешь ошибкой — это не ошибка. Это просто неожиданный эффект.
Re: [Этюд, C#] Overload resolution
От: Andir Россия
Дата: 05.02.09 22:50
Оценка: +1
Здравствуйте, nikov, Вы писали:

N>Скомпилируется ли этот код?

[code skipped]

Итак, поведение компилятора выглядит обоснованным. Но всё равно остаётся какое-то неуловимое ощущение общей нелогичности такого поведения.

Архитектурное решение: Вывод типа лямбды на основе перебора возможных вариантов.
К чему это приводит: нарушение инвариантности имени переменной (от её имени зависит "правильность" программы), невозможности рефакторинга "Rename" в некоторых случаях.
Вопрос: А есть ли примеры подобного нарушения в языке C#, не относящиеся к выводу типа в лямбдах?

Дополнительно: Считаешь ли ты подобный выбор разработчиков обоснованным?

P.S. Логичным продолжением развития такого поведения является очевидно распространение подхода на содержимое методов. Думаю будет много интересных эффектов

С Уважением, Andir!
using( RSDN@Home 1.2.0 alpha 4 rev. 1135 ) { /* Работаем */ }
Re[5]: [Этюд, C#] Overload resolution
От: Undying Россия  
Дата: 06.02.09 06:53
Оценка:
Здравствуйте, nikov, Вы писали:

VD>>В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее.

N>Только все происходило наоборот: я прочитал спецификацию, понял, что она приводит к таком интересному эффекту, а только потом написал код и убедился, что он работает, как предписано спецификацией. Никаких ошибок нет.

Ошибочное поведение внесенное в документацию не перестает быть ошибочным.
Re[7]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.02.09 13:15
Оценка:
Здравствуйте, nikov, Вы писали:

N>Как бы не так. Вывод из инициализации — это только для переменных, объявленных через var. Для параметров в лямбдах тип может выводиться из анализа использования этих параметров (куда они передаются, какие методы вызываются, какие операторы применяются, к чему они кастятся и т.д.). В этом случае строится дерево всевозможных вариантов и производится перебор. Я достаточно хорошо разбираюсь в этом процессе, и много обсуждал его и с его разработчиками в Mirosoft, и с теми, кто его реализовывал для ReSharper в JetBrains.


Значит недостаточно. Для лямбд, в C#, тип параметров (если он не задан явно) всегда берется из места куда он подставляется. Это может быть параметр функции или переменная. Вывода типов из использования в C# нет. Так что тип возвращаемого значения можно вывести если известны все типы параметров лямбды. Но не зная хотя бы одного типа параметра компилятор будет бессилен.

Попробуй на досуге повторить на C# вот этот немерловый код:
    def f1 = d => d.AddDays(1D);
    WriteLine(f1(DateTime.Now));
    
    def f2 = d => d.AddDays(1D);
    _ = f2((d => d.AddDays(1D))(DateTime.Now));
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.02.09 13:20
Оценка:
Здравствуйте, Алексей., Вы писали:

А>Здравствуйте, VladD2, Вы писали:


VD>>Это шарпа то? Гы-гы. Ты видимо читал блоги каких-то других программистов. В Шарпе линейный вывод типов (только из инициализации).


А>Я так понимаю реализация 7.4.2.11 Inferred return type порождает комбинаторику аналогичную приведенную nikov'ым примером.


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

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

VD>>Вот при выводе типов из использования (как в Немерле) разрешение перегрузок и правда является NP-полной задачей. Но и там можно применять эвристики делающие процесс разрешения перегрузок приемлемо бысрым.


А>Это значит что найти пример который отправит в спячку Nemerle несколько сложнее.


Потенциально — конечно. Достаточно добиться перемножения в несколько миллионов вариантов. Но на прктике такого не встретишь. Приведенный же пример весьма реален.
Но мы то ведь о конкретном случае говорили?
Так что — это просто баг или не качественная реализация. Компилятор явно зацикливается. А с чего бы это может произойти?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.02.09 13:25
Оценка:
Здравствуйте, nikov, Вы писали:

N>Когда я говорю про overload resolution в C#, у меня не бывает ошибок


Правильнее говорить — не бывало.

В антропологическом музее:
— А здесь был хрен!
— Не был, а бывал. Это женский скелет!


N>Я тебе уже несколько раз объяснил, почему то, что ты считаешь ошибкой — это не ошибка. Это просто неожиданный эффект.


Ну, я тебе все сказал. Можешь заблуждаться и дальше.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.02.09 13:27
Оценка:
Здравствуйте, Undying, Вы писали:

VD>>>В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее.

N>>Только все происходило наоборот: я прочитал спецификацию, понял, что она приводит к таком интересному эффекту, а только потом написал код и убедился, что он работает, как предписано спецификацией. Никаких ошибок нет.

U>Ошибочное поведение внесенное в документацию не перестает быть ошибочным.


А в документации как раз вроде как все ОК. Там же сказано, что все это относится для переменных тип и название которых совпадает с перекрытым типом.

В прочем, по сути я согласен.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 07.02.09 13:46
Оценка: 1 (1)
Здравствуйте, Алексей., Вы писали:

А>Здравствуйте, VladD2, Вы писали:


VD>>Это логическая ошибка. В указаном пункте нигде не сказано, что допускается интерпретировать имя переменной как ее тип.

А>7.5.4.1 так прямо и говорит выражение simple-name (в данном случае T), которое по сути всегда является идентификатором, в определенном случае может интерпретироваться как имя типа.
А>

А>In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.


Упорством с которым вы игнорируете выделенный текст меня поражает. Я все же услышу комментарий по поводу выделенного мной текста:

In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.


А>Декларацию лямбды можно записать как: void (? T)


А>Сопоставляя параметры делегата и параметры лямбды получаем что параметр T лямбды имеет тип T.


С чего бы это?
Все что можно с уверенностью утверждать, что выполняются следующие правила:
1. Параметр Т совпадает по имени с типом Т.
2. Тип параметра T неизвестен.

По 7.5.4.1 должно выполняться еще одно правило. Тип переменной должен иметь то же тип, что и перекрываемый.

Вот простой пример демонстрирующий идею:
using System;

class Program
{
  public static Program Program;

  static void TestStatic() { }
  void Test() { }

  static void Main(string[] args)
  {
    Program.TestStatic();
    Program.Test();
  }
}

Этот код отличо компилируется и работает.
Если мы теперь добавим еще один класс содержащий такой же экзеплярный метод и заменим тип переменной на него:
using System;

class A
{
  public void Test() { }
}

class Program
{
  public static A Program;

  static void TestStatic() { }
  void Test() { }

  static void Main(string[] args)
  {
    Program.TestStatic();
    Program.Test();
  }
}

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

Возвращаясь к исходному примеру, совершенно ясно, что тип переменной T вывести нельзя (так как он не задается ни явно, ни опосредовано). Стало быть правило 7.5.4.1 применять нельзя и даже вызов статического метода будет ошибкой. А уж типизация лямбды на этом основании просто нонсенс.

В общем, вы защищаете банальны баг подсистемы вывода типов шарпа.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 08.02.09 12:48
Оценка:
Здравствуйте, koandrew, Вы писали:

K>P.S. Тем, кто осилил §7.4 (особенно §7.4.2) ИМХО надо памятник при жизни ставить — без ящика водки не разберёшься


Если там есть что-то непонятное, обращайся, постараюсь объяснить.
Re[5]: [Этюд, C#] Overload resolution
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 08.02.09 18:19
Оценка:
Здравствуйте, nikov, Вы писали:

N>Если там есть что-то непонятное, обращайся, постараюсь объяснить.


Не то чтобы непонятно что-то конкретное — просто в целом эта часть как-то мутно написана. Иногда приходится одно предложение по десять раз перечитывать, чтобы понять, о чём собственно речь. Но за предложение спасибо — я как раз запланировал глубокое изучение этой части, потому как твой этюд показал, что я нихрена в этой части не понимаю
[КУ] оккупировала армия.
Re[6]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 08.02.09 20:06
Оценка: 224 (8) +2 :)))
Здравствуйте, VladD2, Вы писали:

VD>Причем я пробовал применять два алгоритма и оба приводят к багу. Вот они:

VD>1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo.
Откуда требование про не статичность члена?

VD>На мой взгляд баг чистой воды. Не в компиляторе, так в стандарте.

Я готов обсудить вопросы неудачных решений в стандарте после того, как мы убедимся, что здесь нет ошибок реализации, т.е. поведение компилятора соответствует текущей версии стандарта. Нельзя сказать, что я в восторге от того, как overload resolution взаимодействует с лямбдами.

VD>Попробуй обдумать простую мысль. T и S в приведенном коде — это не типы, а имена параметров лябд. Таким образом обращение к ним через точку должно приводить к поиску экзеплярных методов с именем Foo. Но таковых нет!

Опять не пойму, откуда ты берешь требование про экземплярность.

VD>У тебя будет два подходящих кандидата. А поведение компилятора C# ошибка.

Почему ты считаешь, что они оба будут подходящими? Как я покажу ниже, это не так.

VD>И как компилятор выводит, что у параметра с именем T тип T? Это не вывод типов, а телепатия.

VD>Все что сказано в 7.5.4.1 — это то, что если свойство, параметр и т.п. перкрывают тип, то все равно можно обратиться к его статическим членам через имя типа.
VD>Но на каком основании делается вывод о том, что у параметра T тип T, а у параметра S тип S?

Как я понял, твое основное возражение заключается в том, что 7.5.4.1 не может применяться, потому что он требует, чтобы имя параметра совпадало с именем его типа, а в моем примере неизвестно, что параметр T имеет тип T. Это не так. Сейчас я продемонстрирую, почему в момент применения правила 7.5.4.1 известно, что параметр T имеет тип T. Попробуй на время отложить свою интуицию, воспитанную на немерле, и внимательно проследить по спецификации C#, что здесь происходит. Для удобства я пронумеровал шаги рассуждения. Если тебе не понятно, что происходит на некотором шаге, укажи его номер.

Компилятор видит это выражение:
Bar(T => T.Foo())

Сначала он определяет, что такое Bar. Это оказывается группа методов, состоящая из двух методов с сигнатурами Bar(Action<T>) и Bar(Action<S>). Значит, все выражение целиком — это method invocation. Алгоритм дальнейших действий описан в 7.5.5.1 Method invocations.
1) Первым делом, надо составить множество методов-кандидатов. Для этого надо перебрать все методы в группе методов Bar.
1.1) Рассматриваем метод Bar(Action<T>). Это не-generic метод, поэтому для добавления его во множество кандидатов должны выполняться следущие условия:
1.1.1) При вызове, у группы методов Bar должен отсутствовать список типов-аргументов. Условие выполняется.
1.1.2) Метод Bar(Action<T>) должен быть применим к списку аргументов (T => T.Foo()). Правила проверки применимости описаны в 7.4.3.1 Applicable function member. Так как метод не имеет модификатора params, то чтобы он был применим, должны выполняться следующие условия:
1.1.2.1) Количество аргументов должно совпадать с количеством параметров. Условие выполняется (1=1).
1.1.2.2) Если у параметра нет модификаторов ref/out, то их не должно быть и у аргумента. Условие выполняется.
1.1.2.3) Должно существовать неявное преобразование от аргумента T => T.Foo() к типу параметра Action<T>. В данном случае аргумент является лямбда-выражением, поэтому правила, определяющие наличие неявного преобразования, описаны в 6.5 Anonymous function conversions. Внимание! Проверяя наличие неявного преобразования, компилятор совершенно не заботится о том, что overload resolution для метода Bar еще не завершен, или что где-то есть еще метод с сигнатурой Bar(Action<S>). До него дело еще дойдет. Сейчас он просто сопоставляет выражение T => T.Foo() с типом Action<T> по приведенным ниже правилам. Чтобы неявное преобразование существовало, должны выполняться следующие условия:
1.1.2.3.1) Количество параметров в списке параметров лямбды и делегата должны совпадать. Условие выполняется (1=1).
1.1.2.3.2) Если список параметров лямбды не содержит явных указаний типов, то делегат не должен иметь ref/out параметров. Условие выполняется.
1.1.2.3.3) Теперь компилятор дает каждому из параметров лямбды тип соответствующего параметра делегата. То есть, единственный параметр T получает тип T. После того, как параметры получили типы, компилятор проверяет тело лямбды на корректность ("If... when each parameter of F is given the type of the corresponding parameter in D, the body of F is a valid expression"). Тело лямбды — это выражение T.Foo(), где T — это параметр типа T. Это выражение обрабатывается следующим образом:
1.1.2.3.3.1) Надо определить, что такое T.Foo. Синтаксически это member-access. Правила его обработки приведены в 7.5.4 Member access. Так как T — это переменная типа T, то следует выполнить member lookup в типе T. Правила member lookup приведены в 7.3 Member lookup.
1.1.2.3.3.1.1) Определим множество доступных членов с именем Foo в типе T. Внимание! Здесь нигде не написано, что ищутся только экземплярные (не статические) члены. Ищем public члены с именем Foo, определенные в типе T или его базовом типе object. Находится один статический метод.
1.1.2.3.3.1.2) Выбрасываем все члены, имеющие модификатор override. Таких нет.
1.1.2.3.3.1.3) Выбрасываем все nested типы, которые имеют типы-параметры в своей декларации. Таких нет.
1.1.2.3.3.1.4) Так как выражение T.Foo является частью выражения T.Foo(), которое синтаксически является invocation-expression, то член Foo считается invoked.
Поэтому выбрасываем все non-invocable члены. Таких нет.
1.1.2.3.3.1.5) Для каждого члена во множестве, помечаем на удаление все члены, которые он скрывает. На данный момент у нас во множестве есть один член — статический метод Foo(), поэтому помечаем все члены, которые он скрывает. Таких нет.
1.1.2.3.3.1.6) Помечаем на удаление скрытые члены интерфейсов. Таких нет, поэтому ничего не делаем.
1.1.2.3.3.1.7) Удаляем члены, помеченые на удаление. Таких нет.
1.1.2.3.3.1.8) Определяем результат member lookup. Так как множество состоит из одного статического метода Foo(), то результатом будет группа методов, состоящая из этого метода.
Возвращаемся к 1.1.2.3.3.1) Таким образом, T.Foo — это группа методов, состоящая из одного метода.
1.1.2.3.3.2) Значит T.Foo() — это method invocation. Алгоритм дальнейших действий описан в 7.5.5.1 Method invocations.
1.1.2.3.3.2.1) Первым делом, надо составить множество методов-кандидатов. Для этого надо перебрать все методы (то есть, проверить единственный метод) в группе методов T.Foo. Рассматриваем статический метод Foo(), определенный в типе T. Это не-generic метод, поэтому для добавления его во множество кандидатов должны выполняться следущие условия:
1.1.2.3.3.2.1.1) При вызове, у группы методов T.Foo должен отсутствовать список типов-аргументов. Условие выполняется.
1.1.2.3.3.2.1.2) Метод Foo() должен быть применим к списку аргументов (). Правила проверки применимости описаны в 7.4.3.1 Applicable function member. Так как метод не имеет модификатора params, то чтобы он был применим, то количество аргументов должно совпадать с количеством параметров. Условие выполняется (0=0). Так как аргументов ноль, то больше никаких требований нет.
Возвращаемся к 1.1.2.3.3.2.1) Так как все условия выполнены, метод Foo() попадает во множество методов-кандидатов. Других методов в рассматриваемой группе методов нет, поэтому множество методов-кандидатов состоит из одного статического метода Foo(), определенного в типе T.
1.1.2.3.3.2.2) Множество методов-кандидатов урезается, чтобы содержать методы только из наиболее производных типов. Так как метод Foo() определен в классе T, то удаляем все методы, определенные в его базовом типе object и в любых интерфейсах. Таких нет.
1.1.2.3.3.2.3) Определяем лучший метод с помощью правил приведенных 7.4.3 Overload resolution. Так как мы имеем единственный метод, то он автоматически становится лучшим.
1.1.2.3.3.2.4) Производим final validation выбранного метода. Внимание! Именно на этом шаге впервые проверяется, является ли метод статическим или экземплярным. Здесь срабатывает правило 7.5.4.1: доступ идет через точку после параметра T, но этот параметр имеет тип T, поэтому допустимы и статические, и экземплярные члены. То есть наш статический метод Foo() проходит валидацию. Кстати, на этом же шаге проверялись бы и констрейнты на типах-параметрах лучшего метода, если бы он был generic.
Возвращаемся к 1.1.2.3.3.2) Значит, T.Foo() — это корректное выражение.
Возвращаемся к 1.1.2.3.3) Значит, во время анализа тела лямбды не было обнаружено ошибок, поэтому тело лямбды корректно. Теперь проверяем совпадение возвращаемого типа лямбды и делегата. возвращаемый тип делегата — void, поэтому просто требуется, чтобы тело лямбды было выражением, синтаксически допустимым в качестве statement-expression (проверяется по 8.6 Expression statements). Тело лямбды — это invocation-expression, поэтому оно проходит проверку.
Возвращаемся к 1.1.2.3) Таким образом, неявное преобразование от выражения T => T.Foo() к типу Action<T> существует.
Возвращаемся к 1.1.2) Таким образом, метод Bar(Action<T>) применим к списку аргументов (T => T.Foo()).
Возвращаемся к 1.1) Так как все условия выполнены, метод Bar(Action<T>) попадает во множество методов-кандидатов.
1.2) Рассматриваем метод Bar(Action<S>). Это не-generic метод, поэтому для добавления его во множество кандидатов должны выполняться следущие условия:
1.2.1) При вызове, у группы методов Bar должен отсутствовать список типов-аргументов. Условие выполняется.
1.2.2) Метод Bar(Action<S>) должен быть применим к списку аргументов (T => T.Foo()). Правила проверки применимости описаны в 7.4.3.1 Applicable function member. Так как метод не имеет модификатора params, то чтобы он был применим, должны выполняться следующие условия:
1.2.2.1) Количество аргументов должно совпадать с количеством параметров. Условие выполняется.
1.2.2.2) Если у параметра нет модификаторов ref/out, то их не должно быть и у аргумента. Условие выполняется.
1.2.2.3) Должно существовать неявное преобразование от аргумента T => T.Foo() к типу параметра Action<S>. В данном случае аргумент является лямбда-выражением, поэтому правила, определяющие наличие или отсутствие неявного преобразования, описаны в 6.5 Anonymous function conversions. Чтобы неявное преобразование существовало, должны выполняться следующие условия:
1.2.2.3.1) Количество параметров в списке параметров лямбды и делегата должны совпадать. Условие выполняется (1=1).
1.2.2.3.2) Так как список параметров лямбды неявно типизирован, то спискок делегат не должен иметь ref/out параметров. Условие выполняется.
1.2.2.3.3) Теперь компилятор дает каждому из параметров лямбды тип соответствующего параметра делегата. Внимание! Тот факт, что эта лямбда раньше проверялась на соответствие с другим делегатом, и параметр T уже получил ранее другой тип, никакого значения не имеет. Теперь единственный параметр T получает тип S, и начинается новый, полностью независимый процесс анализа тела лямбды. Тело лямбды — это выражение T.Foo(), где T — это параметр типа S. Это выражение обрабатывается следующим образом:
1.2.2.3.3.1) Этот шаг дословно совпадает с шагом 1.1.2.3.3.1 с заменой типа T на тип S. Таким образом, T.Foo — это группа методов, состоящая из одного метода.
1.2.2.3.3.2) Значит T.Foo() — это method invocation. Алгоритм дальнейших действий описан в 7.5.5.1 Method invocations.
1.2.2.3.3.2.1-3) Эти шаги дословно совпадают с шагами 1.2.2.3.3.2.1-3 с заменой типа T на тип S. Статический метод Foo(), определенный в типе S, становится лучшим.
1.2.2.3.3.2.4) Производим final validation выбранного метода. В данном случае правило 7.5.4.1 неприменимо: доступ идет через точку после параметра T, но этот параметр имеет тип S. Поэтому лучший метод должен быть экземплярным. Но тот метод, который мы нашли — статический, поэтому он не проходит валидацию.
Возвращаемся к 1.2.2.3.3.2) Значит, T.Foo() — это некорректное выражение.
Возвращаемся к 1.2.2.3.3) Значит, во время анализа тела лямбды были обнаружены ошибки, поэтому тело лямбды некорректно.
Возвращаемся к 1.2.2.3) Таким образом, неявное преобразование от выражения T => T.Foo() к типу Action<S> не существует.
Возвращаемся к 1.2.2) Таким образом, метод Bar(Action<S>) не применим к списку аргументов (T => T.Foo()).
Возвращаемся к 1.2) Метод Bar(Action<S>) не попадает во множество методов-кандидатов.
Возвращаемся к 1) Таким образом, множество методов-кандидатов состоит из одного метода Bar(Action<T>).
2) Множество методов-кандидатов урезается, чтобы содержать методы только из наиболее производных типов. Так как метод Bar (Action<T>) определен в классе Program, то удаляем все методы, определенные в его базовом типе object и в любых интерфейсах. Таких нет.
3) Определяем лучший метод с помощью правил приведенных 7.4.3 Overload resolution. Так как мы имеем единственный метод, то он автоматически становится лучшим.
4) Производим final validation выбранного метода. Так как доступ идет через simple-name из статического члена Main, то найденный статический метод Bar(Action<T>) проходит валидацию.
Теперь надо определить, какой же тип имеет параметр T у аргумента T => T.Foo(). Согласно 7.14 Anonymous function expressions, этот тип определяется типом параметра делегата, к которому приводится данная лямбда. Так как overload resolution выбрал метод Bar(Action<T>), то лямбда приводится к типу Action<T>, и параметр T окончательно получает тип T. Опять-таки, здесь совершенно не важно, что ранее были попытки сопоставления этой лямбды с другими делегатами. Повторив шаги 1.1.2.3.3.1-2, мы узнаем, что T.Foo() в теле лямбды — это корректное выражение, вызов статического метода, определенного в типе T.

Теперь переходим к анализу выражения
Bar(S => S.Foo())

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

Остались какие-то неясности?
Re[6]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 08.02.09 20:21
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Это потому, что в компиляторы такие алгоритмы используются. Тот же пример на Немерле компилируется в мгновение ока. И интеграция на нем работает отлично:

VD>
VD>using System;

VD>module Program
VD>{
VD>    Main() : void
VD>    {
VD>      _ = Foo(a => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => a))))))));
VD>    }

VD>    Foo(_ : Func[byte, byte]) : string { null }
VD>    Foo(_ : Func[short, short]) : string { null }
VD>    Foo(_ : Func[int, int]) : string { null }
VD>    Foo(_ : Func[long, long]) : string { null }
VD>    Foo(_ : Func[string, string]) : string { null }
VD>}

VD>Если подвести мышку к параметру "a", то молниеносно выведится "(function parametr) a : string".

К сожалению, nemerle.org пока в дауне, и у меня под рукой есть только версия от марта 2008. Она намертво зависает на этом примере. Я рад, если это улучшилось за год. Я предлагал варианты ускорения этих алгоритмов C# compiler team, которые должны были сделать компиляцию этого конкретного примера (и схожих с ним) молниеносной, но они решили, что дешевле иметь простой (хотя и медленный на экзотических примерах) алгоритм, чем хитроумный, в котором могут быть трудноуловимые баги, и который все равно не обеспечит компиляцию всех вариантов кода за линейное к числу перегрузок время.

Иннтересно, что несколько месяцев назад ты утверждал
Автор: VladD2
Дата: 01.10.08
, что этот код не компилируется и не должен компилироваться в немерле.
Re[8]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 08.02.09 20:37
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Значит недостаточно. Для лямбд, в C#, тип параметров (если он не задан явно) всегда берется из места куда он подставляется. Это может быть параметр функции или переменная. Вывода типов из использования в C# нет.

Но "место, куда он подставляется" может быть параметром перегруженного метода. В этом случае использование параметра лямбды в теле лямбды может определить, какой именно перегуженный метод будет вызван, и какой тип, в конечном счете, получит параметр лямбды. Помнишь пример, который я приводил
Автор: nikov
Дата: 05.02.09
в форуме по немерле? Вот, как с ним справляется C#:
using System;

class Program
{
    static void Main()
    {
        Foo(x => Console.WriteLine(x.Message));       
    }

    static void Foo(Action<Exception> _) {}
    static void Foo(Action<string> _) {}
}

Как видишь, вывод типов из использования в C# все-таки есть. Но он ограничивается анализом использования внутри лямбд.

VD>Попробуй на досуге повторить на C# вот этот немерловый код:

Да, я осведомлен, что есть многие варианты, которые C# типизировать не в состоянии.
Re[7]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.02.09 06:24
Оценка:
Здравствуйте, nikov, Вы писали:

N>Я готов обсудить вопросы неудачных решений в стандарте после того, как мы убедимся, что здесь нет ошибок реализации, т.е. поведение компилятора соответствует текущей версии стандарта. Нельзя сказать, что я в восторге от того, как overload resolution взаимодействует с лямбдами.


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

Кстати, о перегрузках. Как в F# можно взывать метод Where со следующей перегрузкой:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)

для списка кортежей:
let xs = [(1, "1"), (2, "2")];


N>Как я понял, твое основное возражение заключается в том, что 7.5.4.1 не может применяться, потому что он требует, чтобы имя параметра совпадало с именем его типа, а в моем примере неизвестно, что параметр T имеет тип T. Это не так. Сейчас я продемонстрирую, почему в момент применения правила 7.5.4.1 известно, что параметр T имеет тип T. Попробуй на время отложить свою интуицию, воспитанную на немерле, и внимательно проследить по спецификации C#, что здесь происходит. Для удобства я пронумеровал шаги рассуждения. Если тебе не понятно, что происходит на некотором шаге, укажи его номер...


N>...


N>Остались какие-то неясности?


Убедил. Ты прав!

Мня смутило имя. Почему-то воспринимал T в Action<T> как дженерик-аргумент. От того и не мог понять откуда берется ассоциация типа Т с параметром Т.

Однако такое поведение выглядит странно. В Немерле пункт 7.5.4.1 не реализован, по этому он подобный код не скомпилирует. Но вот такой вариант:
using System;

module Program
{
  Main() : void
  {
    Bar(T => { T.Foo(); });
    Bar(S => { S.Bar(); });
    
    Bar(t => { t.Foo(); });
    Bar(s => { s.Bar(); });
  }
  
  Bar(_ : Action[T]) : void { Console.WriteLine("T"); }
  Bar(_ : Action[S]) : void { Console.WriteLine("S"); }
}

class T
{
  public Foo() : void { }
}

class S
{
  public Bar() : void { }
}

...скомпилирует.

Так что если в Немерле реализовать 7.5.4.1, то и он будет вести себя "так странно".

В принципе 7.5.4.1 иногда удобен. Но подобные эффекты мне совсем не нравятся.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.02.09 06:36
Оценка: +1
Здравствуйте, nikov, Вы писали:
зования в C# нет.
N>Но "место, куда он подставляется" может быть параметром перегруженного метода. В этом случае использование параметра лямбды в теле лямбды может определить, какой именно перегуженный метод будет вызван, и какой тип, в конечном счете, получит параметр лямбды. Помнишь пример, который я приводил
Автор: nikov
Дата: 05.02.09
в форуме по немерле? Вот, как с ним справляется C#:

N>
N>using System;

N>class Program
N>{
N>    static void Main()
N>    {
N>        Foo(x => Console.WriteLine(x.Message));       
N>    }

N>    static void Foo(Action<Exception> _) {}
N>    static void Foo(Action<string> _) {}
N>}
N>

N>Как видишь, вывод типов из использования в C# все-таки есть. Но он ограничивается анализом использования внутри лямбд.

Это не вывод типов из использования. Тип берется из типа аргумента Foo.
Приведенные примеры немерлового кода не по зубам Шарпу именно из-за отсутствия вывода типов из использования.

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

VD>>Попробуй на досуге повторить на C# вот этот немерловый код:

N>Да, я осведомлен, что есть многие варианты, которые C# типизировать не в состоянии.

Дык — это именно потому, что Шарп не может взять тип задним числом. Ему всегда нужно давать тип в самом начале. Явно, не явно без разницы. Главное, что тип обязан быть.

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

Кстати, это нехило мешает макросам. В макрах иногда полезно знать тип, но тип может быть неизвестен очень долгое время. Есть конечно механизмы позволяющие дождаться момента когда тип станет известен, но их использование тоже усложняет реализацию. Потом об этом банально надо помнить. В общем, во многих радостях многие печали.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.02.09 06:49
Оценка:
Здравствуйте, nikov, Вы писали:

N>К сожалению, nemerle.org пока в дауне,


Да, уж.

N>и у меня под рукой есть только версия от марта 2008. Она намертво зависает на этом примере.


Последняя версия точно работает. Могу скриншо вывесить или дать ссылку на зеркало СВН-на на нашем сайте.

N> Я рад, если это улучшилось за год. Я предлагал варианты ускорения этих алгоритмов C# compiler team, которые должны были сделать компиляцию этого конкретного примера (и схожих с ним) молниеносной, но они решили, что дешевле иметь простой (хотя и медленный на экзотических примерах) алгоритм, чем хитроумный, в котором могут быть трудноуловимые баги, и который все равно не обеспечит компиляцию всех вариантов кода за линейное к числу перегрузок время.


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

N>Иннтересно, что несколько месяцев назад ты утверждал
Автор: VladD2
Дата: 01.10.08
, что этот код не компилируется и не должен компилироваться в немерле.


И правда!
Значит, то что он теперь работает — это моих рук дело. Я тут в последнее время доработал алгоритм разрешения перегрузки Немерла. Цель как раз была сделать его по поведению ближе к Шарпу. Это было нужно для того, чтобы можно было пользоваться ЛИНК-ом.

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

В общем, работает. И на данном примере весьма шустро.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.02.09 06:51
Оценка:
Здравствуйте, Undying, Вы писали:

U>Ошибочное поведение внесенное в документацию не перестает быть ошибочным.


Ну, тут палка о двух концах. С одной стороны удобство, с другой логичность и не противоречивость поведения.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.