Здравствуйте, koandrew, Вы писали:
K>Здравствуйте, nikov, Вы писали:
N>>Скомпилируется ли этот код?
K>Да, и выведет K>Т K>S
Можно подумать, что у nikov-а под рукой нет компилятора и он обращается к форумчанам за помощью в поиске ответа на вопрос. А чтобы никто не догадался, что это он с телефона запостил, приписывает [Этюд] в заголовок.
Может будут какие-то соображения, почему этот код компилируется, а главное, почему выводит T и S?
Здравствуйте, 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) ИМХО надо памятник при жизни ставить — без ящика водки не разберёшься
Здравствуйте, 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, а также компилятор соображает, что в лямбдах речь идёт о статических методах, но это уже ничего не меняет, т.к. этап выведения типов уже завершён.
Здравствуйте, 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 заметно усложнится за счет появляния ковариантности и контравариантности и более гибких механизмов поиска подходящих типов.
Здравствуйте, nikov, Вы писали:
N>Кстати, в спецификации для C# 4.0 раздел 7.4.2 заметно усложнится за счет появляния ковариантности и контравариантности и более гибких механизмов поиска подходящих типов.
А когда будет доступна предварительная спецификация на C# 4.0?
Здравствуйте, nikov, Вы писали:
N>Скомпилируется ли этот код?
Ну, ты бы вряд ли создал бы этюд по этому поводу если бы код скомпилировался бы.
И на мой взгляд — это натуральный баг.
Причем я пробовал применять два алгоритма и оба приводят к багу. Вот они:
1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo.
2. Компилятор понимает, что в T и S нужного члена нет и пытается вызвать статические члены принимая T и S за типы. При этом должна получиться неоднозначность лябды, так как оба вызова метода T.Foo и S.Foo никак не влияют на тип самой лябды.
Если ты считаешь, что это не баг, то поясни логику используемую компилятором Шарпа.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Ну, ты бы вряд ли создал бы этюд по этому поводу если бы код скомпилировался бы.
Я не понял, он у тебя не компилируется что ли?
VD>И на мой взгляд — это натуральный баг.
Нет, здесь нет бага. Но объясню попозже. Может быть, кто-то все-таки сам разберется.
Здравствуйте, 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# это компилирует без вопросов, но результат будет неожиданный
Попробую сделать предположения:
1. Какой из вариантов T или S определяются перебором. Сначала в лямбда-выражение подставляется вариант с типом T, потом вариант с типом S. Тот из вариантов который успешно скомпилировался тот и считается подходящим.
2. Согласно пункту 7.5.4.1 используется статический вызов метода Foo(), а не вызов через параметр.
Сгенрилось два метода, получающих XmlWriter, второй Bar() ни разу не использован..
Даже в тултипе над XmlReader.Flush() написано "(parameter) XmlWriter XmlReader"
Здравствуйте, Пельмешко, Вы писали:
П>Думаю дело в записи лямбды П>Запись выше — всего сокращение от этой, полученная путём опускания имени неиспользуемого параметра:
Не-не-не, так не бывает. В лямбде можно опускать типы параметров, но не их имена (см. 7.14 Anonymous function expressions).
В спецификации так до сих пор четко и не сформулировано что же такое valid expression и valid block в контексте преобразования неявно типизированной анонимной функции к делегату (раздел 6.5).
Правильно ли я понимаю что это просто "компиляция" в контексте? Если выражение или блок успешно "скомпилировались" то выражение считается валидным, если неуспешно — невалидным?
Здравствуйте, Алексей., Вы писали:
А>В спецификации так до сих пор четко и не сформулировано что же такое valid expression и valid block в контексте преобразования неявно типизированной анонимной функции к делегату (раздел 6.5).
Да, к сожалению, нет ясного определения.
А>Правильно ли я понимаю что это просто "компиляция" в контексте? Если выражение или блок успешно "скомпилировались" то выражение считается валидным, если неуспешно — невалидным?
Почти так. На самом деле некоторые ошибки компиляции игнорируются при определении валидности:
1) "Плохие" expression trees (содержащие элементы, непредставимые в текущей объектной модели)
2) Проблемы, связанные с reachability и definite assignment (как ни странно, в некоторых случаях они тоже зависят от типов параметров лямбды)
3) Ошибки связанные с использованием unsafe кода в итераторах
4) Предупреждения, превратившиеся в ошибки из-за опции Treat warnings as errors.
5) Проблемы с метками и goto
Этот список появился в результате большого количества экспериментов и распросов разработчика, который это реализовывал. К сожалению, это сообщение — это пока единственное место, где он приведен. И нет гарантий, что я что-то не упустил из виду.
Здравствуйте, Блудов Павел, Вы писали:
БП>Здравствуйте, VladD2, Вы писали:
VD>>1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo. БП>Фишка в том, что это не имена.
Всё таки это имена, иначе вот это было бы неверно и не должно было бы компилироваться:
Тут фишка в том, что у XmlReader нет метода Flush, ибо незачем.
А проблема, обсуждаемая в этой теме, как я понял, в том, что компилятор нарушает принцип "либо дудочка, либо кувшинчик".
Причём из самых благих побуждений — он пытается максимально угодить потребителям.
В итоге мы потенциально имеем целый ряд проблем, связанный с разного рода "наведёнками".
В приведённом выше примере добавление статического метода Flush в клаcc XmlReader или добавление локального класса XmlReader приведёт к вызову второго Bar вместо первого. Плохо это не само по себе, а потому, что компилятор делает это молча. Отловить такие изменения в поведении можно только при 100% покрытии всех методов тестами. А такой подход к разработке ПО неконкурентноспособен.