[Этюд, 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>>
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.