Почему нет неявного типа?

Думаю, я понимаю, почему это небольшое консольное приложение С# не компилируется:

using System;

namespace ConsoleApp1
{
    class Program
    {
        static void WriteFullName(Type t)
        {
            Console.WriteLine(t.FullName);
        }

        static void Main(string[] args)
        {
            WriteFullName(System.Text.Encoding);
        }
    }
}

Компилятор вызывает ошибку CS0119: 'Encoding' is a type which is not valid in the given context. Я знаю, что я могу создать объект Type из его имени с помощью оператора typeof():

        ...
        static void Main(string[] args)
        {
            WriteFullName(typeof(System.Text.Encoding));
        }
        ...

И все работает так, как ожидалось.

Но для меня такое использование typeof() всегда казалось несколько избыточным. Если компилятор знает, что какой-то токен является ссылкой на данный тип (как предлагает ошибка CS0119), и он знает, что назначение какого-либо присвоения (будь то параметр функции, переменная или что-то еще) ожидает ссылки на данный тип, почему не может ли компилятор воспринимать его как неявный вызов typeof()?

Или, может быть, компилятор вполне способен принять этот шаг, но он был выбран не из-за проблем, которые могут возникнуть. Это приведет к каким-либо вопросам двусмысленности/разборчивости, о которых я не могу сейчас думать?

Ответ 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.