Ответ 1
Если компилятор знает, что какой-то токен является ссылкой на данный тип (как предлагает ошибка CS0119), и он знает, что назначение какого-либо присвоения (будь то параметр функции, переменная или что-то еще) ожидает ссылки на данный тип, почему не может ли компилятор воспринимать его как неявный вызов typeof()?
Во-первых, ваше предложение заключается в том, что компилятор одновременно вызывает "внутри навстречу" и "снаружи внутрь". То есть, чтобы ваша предлагаемая функция работала, компилятор должен сделать вывод, что выражение System.Text.Encoding
относится к типу и что контекст - вызов WriteFullName
- требует типа. Как мы знаем, что для контекста требуется тип? Разрешение WriteFullName
требует разрешения перегрузки, потому что их может быть сто, и, возможно, только один из них принимает Type
в качестве аргумента в этой позиции.
Поэтому теперь мы должны разработать разрешение перегрузки, чтобы распознать этот конкретный случай. Слишком большое разрешение перегрузки. Теперь рассмотрим последствия для вывода типа.
С# разработан таким образом, что в подавляющем большинстве случаев вам не нужно делать двунаправленный вывод, потому что двунаправленный вывод является дорогостоящим и сложным. Место, где мы используем двунаправленный вывод, - лямбда, и мне потребовалась большая часть года для его реализации и тестирования. Получение контекстно-зависимого вывода на lambdas было ключевой особенностью, которая была необходима для того, чтобы LINQ работал, и поэтому стоило чрезвычайно высокого бремени получения права на двунаправленный вывод.
Более того: почему Type
особый? Совершенно правомерно сказать object x = typeof(T);
поэтому не должен быть object x = int;
быть законным в своем предложении? Предположим, что тип C
имеет определяемое пользователем неявное преобразование из Type
в C
; не должен C c = string;
быть законным?
Но на мгновение оставь это в стороне и рассмотрите другие достоинства вашего предложения. Например, что вы предлагаете сделать?
class C {
public static string FullName = "Hello";
}
...
Type c = C;
Console.WriteLine(c.FullName); // "C"
Console.WriteLine(C.FullName); // "Hello"
Разве это не c.FullName != C.FullName
вас так странно, что c == C
но c.FullName != C.FullName
? Основным принципом программирования языка программирования является то, что вы можете записать выражение в переменную, а значение переменной ведет себя как выражение, но это совсем не так.
Ваше предложение в основном состоит в том, что каждое выражение, относящееся к типу, имеет другое поведение в зависимости от того, используется оно или назначено, и это очень сложно.
Теперь вы можете сказать, что давайте сделаем специальный синтаксис для устранения неоднозначности ситуаций, когда тип используется из ситуаций, где указан тип, и существует такой синтаксис. Это typeof(T)
! Если мы хотим рассматривать T.FullName
как T
являющийся Type
мы говорим typeof(T).FullName
и если мы хотим рассматривать T
как квалификатор в поиске, мы говорим T.FullName
, и теперь мы четко устраняем эти случаи без сделать любой двунаправленный вывод.
В принципе, основная проблема заключается в том, что типы не являются первоклассными в С#. Есть вещи, которые вы можете делать с типами, которые вы можете делать только во время компиляции. Нет:
Type t = b ? C : D;
List<t> l = new List<t>();
где l
- либо List<C>
либо List<D>
зависимости от значения b
. Поскольку типы являются очень специальными выражениями и, в частности, являются выражениями, которые не имеют значения во время выполнения, им необходимо иметь специальный синтаксис, вызывающий, когда они используются в качестве значения.
Наконец, есть также аргумент в пользу правильной корректности. Если разработчик пишет Foo(Bar.Blah)
и Bar.Blah
- это тип, вероятность довольно хорошая, они допустили ошибку и подумали, что Bar.Blah
- это выражение, которое разрешает значение. Коэффициенты не очень хорошие, что они намеревались передать Type
to Foo
.
Следующий вопрос:
почему это возможно с группами методов при передаче аргументу делегата? Не потому ли, что использование и упоминание метода легче отличить?
Группы методов не имеют членов; вы никогда не говорите:
class C { public void M() {} }
...
var x = C.M.Whatever;
потому что у CM
нет каких-либо членов вообще. Так что проблема исчезает. Мы никогда не говорим "хорошо, что CM
конвертируется в Action
и Action
имеет метод Invoke
поэтому разрешите CMInvoke()
. Это просто не произойдет. Опять же, группы методов не являются значениями первого класса. Только после того, как они преобразуются в делегатов, они становятся значениями первого класса.
В принципе, группы методов рассматриваются как выражения, которые имеют значение, но не имеют тип, а затем правила конвертирования определяют, какие группы методов конвертируются в типы делегатов.
Теперь, если вы собираетесь сделать аргумент о том, что группа методов должна быть неявно конвертируема в MethodInfo
и использоваться в любом контексте, где ожидался MethodInfo
, тогда мы должны были бы рассмотреть достоинства этого. Там было предложение в течение многих десятилетий, чтобы сделать infoof
оператор (произносится как "infoof", конечно!), Который будет возвращать MethodInfo
, когда данный метод группы и PropertyInfo
когда дано свойство и так далее, и что предложение всегда проваливались поскольку слишком много проектных работ для слишком мало пользы. nameof
- это дешевая версия, которая была реализована.
Вопрос, который вы не спрашивали, но который кажется родным:
Вы сказали, что
C.FullName
может быть неоднозначным, потому что было бы непонятно, является лиC
Type
или типомC
Существуют ли другие подобные двусмысленности в С#?
Да! Рассматривать:
enum Color { Red }
class C {
public Color Color { get; private set; }
public void M(Color c) { }
public void N(String s) { }
public void O() {
M(Color.Red);
N(Color.ToString());
}
}
В этом сценарии, остроумно названном "Проблема цвета цвета", компилятору С# удается выяснить, что Color
в вызове M
означает тип, а в вызове N
это означает this.Color
. Сделайте поиск в спецификации на "Цветной цвет", и вы найдете правило, или см. Запись в блоге Color Color.