Здравствуйте, Блудов Павел, Вы писали: A>>Всё таки это имена, иначе вот это было бы неверно и не должно было бы компилироваться:
[code skipped] БП>Тогда и это должно компилироваться, но не делает этого: БП>
Да, конечно, если разрешить неоднозначность (например указав тип переменной), то будет компилироваться. А в предыдущем случае, компилятор каким-то своим умом разрешает эту неоднозначность ("ум" в этом случае активный, а не статично описанный похоже).
БП>Впрочем, я неверное неудачно выразился. "Это более чем имена" — так пойдёт? Компилятор почему-то трактует данный код как БП>
БП>Почему он так делает Возможно это своего рода оптимизация, позволяющая сократить время на перебор вариантов.
Не-е, я думаю это не трактовка имени переменной, а вывод типа так срабатывает. То есть вариант выбора типа, в данном случае, возможен и он единственный, точно также как:
Так можно:
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
}
Здравствуйте, nikov, Вы писали:
VD>>Ну, ты бы вряд ли создал бы этюд по этому поводу если бы код скомпилировался бы. N>Я не понял, он у тебя не компилируется что ли?
Компилируется. Это так называемый сарказм.
VD>>И на мой взгляд — это натуральный баг. N>Нет, здесь нет бага. Но объясню попозже. Может быть, кто-то все-таки сам разберется.
По-моему время уже пришло.
ЗЫ
На мой взгляд баг чистой воды. Не в компиляторе, так в стандарте.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Блудов Павел, Вы писали:
VD>>1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo. БП>Фишка в том, что это не имена. Что интересно, РеШарпер 4.5 предлагает заменить их на
А что же?
И на основании чего вдруг в одном случае это имя параметра, а в другом типа?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
А оно и скомпилируется если устранить неоднозначность:
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() { }
}
Короче, баг чистой воды.
Посему очень хочется послушать объяснение Никова которое чудесным образом изменит мое мнение.
БП>Впрочем, я неверное неудачно выразился. "Это более чем имена" — так пойдёт? \
Не, не пойдет. Более чем — это уже магия. А я, знаете ли, верю в фокусы, но не в магию.
БП>Компилятор почему-то трактует данный код как БП>
БП>Почему он так делает Возможно это своего рода оптимизация, позволяющая сократить время на перебор вариантов.
А с какого рожна делаются такие макроподстановки? Компилятор телепат?
Самое разумное в данном случае было бы сообщить об ошибке, чтобы человек принял меры. А так имеется замазанная проблема которая когда-то обязательно выстрелит.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, 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>)'
Так что это явно странная трактовка имени переменной.
На мой взгляд — это баг чистой воды. По крайне мере поступать так не логично, а значит вредно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Блудов Павел, Вы писали:
БП>А проблема, обсуждаемая в этой теме, как я понял, в том, что компилятор нарушает принцип "либо дудочка, либо кувшинчик". БП>Причём из самых благих побуждений — он пытается максимально угодить потребителям.
Это банальный баг в компиляторе. Вывод типа параметра на основании его имени, имеющий здесь место — это бред сивой кобылы.
Приведенный Андиром пример четко демонстрирует ошибку.
В общем, на мой взгляд Никову давно пора перестать интриговать и объяснить свою позицию. Ведь он считает, что бага нет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, 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
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, koandrew, Вы писали:
K>Попробую пояснить, что я имею в виду. При выводе типа аргумента компилятор смотрит на использование методов объекта внутри лямбды. В нашем случае он видит использование метода типа T в первом случае и типа S во втором. Поскольку на этом этапе компиляции для него статические и экземплярные методы суть одно и то же, он выводит тип Action<T> для первой лямбды и Action<S> для второй. Дальше подключается разрешение перегрузок, и соответственно вызываются методы Bar, а также компилятор соображает, что в лямбдах речь идёт о статических методах, но это уже ничего не меняет, т.к. этап выведения типов уже завершён.
K>Надеюсь, я понятно изъяснил свои мысли...
Понятно только одно. Ты вообще не понял, что тут происходит.
Попробуй обдумать простую мысль. T и S в приведенном коде — это не типы, а имена параметров лябд. Таким образом обращение к ним через точку должно приводить к поиску экзеплярных методов с именем Foo. Но таковых нет!
Компилятор просто не имеет право делать предположений о типах параметров. Он или должен вывести их из инициализации, или сообщить о неоднозначности.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, nikov, Вы писали:
N>Про 7.5.4.1 замечено верно.
ОК.
Все что сказано в 7.5.4.1 — это то, что если свойство, параметр и т.п. перкрывают тип, то все равно можно обратиться к его статическим членам через имя типа.
Но на каком основании делается вывод о том, что у параметра T тип T, а у параметра S тип S?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Не должон — компилятору неоткуда выгрести тип переменной ибо вызывается неизвестно что. Насколько помню синтаксис, static метод не может дёргаться через инстанс. Вот если бы класс был бы статик — тогда точно нет.
Пришло время подытожить почему данный код компилируется и является корректным с точки зрения спецификации.
Т.к. случаи 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 в данном контексте.
Здравствуйте, Алексей., Вы писали:
А>Пришло время подытожить почему данный код компилируется и является корректным с точки зрения спецификации. А>[skipped]
Здравствуйте, Алексей., Вы писали:
А>Преобразование лямбды к типу 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 в данном контексте.
Здравствуйте, 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.
Во втором случае имя параметра и типа несогласованы, поэтому доступ к статическому члену недопустим, что приводит к отсутствию неявного преобразования от лямбды к делегату.
Здравствуйте, nikov, Вы писали: N>Во втором случае имя параметра и типа несогласованы, поэтому доступ к статическому члену недопустим, что приводит к отсутствию неявного преобразования от лямбды к делегату.
Здравствуйте, Алексей., Вы писали:
А>Правильно ли я понимаю что это просто "компиляция" в контексте? Если выражение или блок успешно "скомпилировались" то выражение считается валидным, если неуспешно — невалидным?
Кстати, интересно, что если лямбда содержит вложенные лямбды, то это процесс может вызываться рекурсивно. Я, кажется, когда-то приводил пример, когда это вызывает комбинаторный взрыв:
Угу, где-то в блоках разработчиков говорилось о том что вывод типов является NP-полной задачей. За пример, спасибо, не думал что так просто отправить компилятор в спячку.
Здравствуйте, Алексей., Вы писали:
А>Преобразование лямбды к типу 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.
Подчеркиваю! Тип Е должен быть таким же как тип Е. Другими словами мы должны иметь переменную или свойство имя которой совпадает с именем ее типа. Только при этом условии этот пункт должен срабатывать.
Сделано это чтобы имена переменных и свойств совпадающие с именами типов не скрывали статических членов этого типа.
В данном же случае имеет место неверное предположение о том, что тип параметра с именх Е тоже Е. Но это не верно! Это просто не откуда взять.
Так что компилятор Шарпа ведет себя не в соответствии с этим пунктом, а иначе. Он просто считает, что раз мы можем типизировать тело считая тип параметра равным Е, то значит все ОК. Но это не верное предположение. Программист мог попросту ошибиться.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, 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 имеет тип Т, что в корне не верно.
В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.