Почему мы должны использовать typeof, а не просто использовать этот тип?

При попытке присвоить тип свойству типа System.Type, почему мы не можем этого сделать?

foo.NetType = bool;

Компилятор выдает это предупреждение:

"Ожидаемое выражение.

Способ решения проблемы заключается в следующем:

foo.NetType = typeof(bool);

Мой вопрос: почему мы не можем использовать первый подход? Разве компилятор не достаточно умный, чтобы понять, что мы пытаемся сделать здесь? Есть ли какая-то причина, почему мы должны идти со вторым подходом (typeof)?

Ответ 1

Хороший вопрос - поскольку это интересный вопрос в дизайне языка. Возможно, это не идеальный вопрос для этого сайта, поскольку речь идет не о конкретном, фактическом коде.

Было бы совершенно возможно разработать язык, в котором имя типа может использоваться как выражение без явного оператора typeof.

Для этого потребуется добавить небольшое количество дополнительных правил и разъяснений на язык. Например, предположим, что у нас было:

enum Color { Red }
class Square 
{
    Color Color { get; set; }
    void M()
    {
        Type t = Color.GetType(); 

В С# сегодня это однозначно означает вызов getter для свойства Color и вызов GetType для результата. (См. Раздел "Цветовое правило цвета" для объяснения причин.) В предлагаемом вами мире есть три вещи, которые это может означать:

  • Вызовите getter для Color, вызовите GetType в результате, назначьте объект Type для Color to t.
  • Цвет вызывает объект Type для типа Color. Вызовите GetType для этого типа и назначьте объект Type для System.Type в t.
  • Цвет относится к типу Color, GetType относится к нестационарному методу, и это ошибка, потому что это статический вызов нестатического элемента.

Вы хотите уточнить спецификацию, чтобы было ясно, когда Color в выражении означает тип, а когда это означает, сделать объект типа. Таким образом, предлагаемая функция добавляет небольшое количество двусмысленности, с которой необходимо иметь дело, но это полностью выполнимо. Разработчики языка могли бы разработать разумные правила.

Язык, который позволяет языковому элементу, который обычно является частью анализа времени компиляции, вместо этого интерпретироваться как код, который создает объект, который может управляться кодом в той же программе, называется гомоиконным языком. С# был очень негомоиконным языком до С# 3; деревья выражений сделали С# гораздо более homoiconic. С# 3 позволяют lambdas обрабатываться как программные элементы, которые затем используются для генерации методов, которые выполняют действие тела лямбда, но также поддерживают гомоциконное преобразование из лямбда в объект, представляющий тело лямбда.

Один из самых гомоиконических языков - Lisp; Lisp управляет списками, и любая программа Lisp сама может быть как список; он может быть процитирован и управляться во время выполнения как объекты, а не как код. Итак, еще раз мы видим здесь истину старой шутки о дизайне языка: каждый разработчик языка в конце концов повторно изобретает Lisp, плохо.

Я отвлекаюсь. Тогда ваш вопрос по существу: должен ли С# быть более или менее гомоиконным по отношению к именам типов? Если имя типа имеет одно значение - директиву времени компиляции - в таких контекстах, как

Foo x = new Foo();
object o = new List<Foo>[] {};
Foo.StaticMethod();

и имеют совсем другое значение - как конструкцию объекта, который можно проверить во время выполнения в других контекстах:

object t = Foo; // Assign a Type object to t.

?

Хотя, конечно, можно было бы создать такой язык, мне это не очень нравится. Без того, чтобы оператор там явно вызывал "эй, мы используем то, что обычно является элементом времени компиляции в гомокиноподобной манере", это может сбить с толку. До С# 3 не было других свойств гомоиконного языка, поэтому было бы странно иметь единственное, что типы могут использоваться как типы, так и выражения, которые приводят к типу. И представляется весьма неудачным, что в некоторых выражениях неясно, означает ли конкретное простое имя "я использую этот тип во время компиляции" или "Я хочу сделать объект".

Наличие явного оператора typeof смягчает все эти проблемы; это однозначно, когда тип используется гомоконически и очень ясно читателю.

Чтобы задать очень конкретный вопрос о вашем вопросе:

При попытке назначить тип для свойства типа System.Type...

С# обычно не имеет специальных правил, которые применяются только в назначениях. Значение выражения обычно определяется без обращения к контексту, в котором используется выражение. Когда мы говорим:

x = y;

Мы обычно не говорим "О, я знаю, что y присваивается x, а x имеет тип X, поэтому я буду использовать эту информацию при анализе y". Скорее, анализ идет изнутри во внешний: мы выясняем, что означает y, а затем решаем, совместимо ли оно с X.

Исключение из этого правила, конечно, лямбда; lambdas учитывают свой "целевой" тип, потому что целевой тип можно использовать для вывода типов неявно типизированных лямбда. Правильность этих правил была очень сложной.

В более общем смысле, это плохая идея, чтобы специальные выражения присваивания. Существует множество способов присвоения значений переменным:

M(x); // x is assigned to a formal
q = new [] { x }; // x is assigned to the first element of an array
y = new Y() { X = x }; // x is assigned to a property of Y.

Вы хотите, чтобы правила для всех этих назначений были согласованными; если выражение означает "Я объект типа", то это должно означать, что в каждом контексте, в котором может отображаться это выражение, а не только как правая сторона задания.

Ответ 2

Извините, я сначала не понял вашу цель. Однако вы все еще немного смущены.

Вы пытаетесь назначить экземпляр объекта Type для свойства. bool не является экземпляром класса Type, это его собственный тип. Я ценю, что терминология немного запутанна, но это две разные вещи. Вот почему это не работает.

Ответ 3

Итак, здесь SWAG:

Если у меня есть класс:

public class Foo
{
    public static int Bar = 1;
}

И у меня есть код вроде:

var foo = Foo;

Вы можете сказать "хорошо, это определенно означает тип"

Теперь, если у меня есть что-то вроде:

public class Bar
{
    public void Foo ()
    {
          var hmm = Foo;
    }
}

Что я имею в виду? Тип Foo? Метод Foo? Пространство имен (если оно существует) Foo?

Обернув его в typeof, мы сделаем его явным, что мы хотим. Кроме того, он генерирует конкретный IL, но я предполагаю, что ваш вопрос неявно означает "Почему компилятор не умнее?"