Почему существует столкновение имен между параметром типового типа и другими членами

Иногда полезно иметь что-то вроде:

class X {
  ...
}

class Y {
  X X {
    get { ... }
    set { ... }
  }
}

поскольку X описывает и то, что тип (как имя класса), так и значение, к которому обращаются/мутируются (как имя свойства). Все идет нормально. Предположим, вы хотели сделать то же самое, но в общем виде:

class Z<T> {
  T T {
    get { ... }
    set { ... }
  }
}

В этом примере компилятор жалуется, что: The type 'Z<T>' already contains a definition for 'T'.

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

Краткая версия: Почему работает первый пример, но не второй?

EDIT: Я только что обнаружил, что если я "Рефакторинг > Переименуйте" параметр типа, скажем, от T до U, среда IDE изменит его на:

class Z<U> {
  U T {
    get { ... }
    set { ... }
  }
}

поэтому что-то там знает, какой тип и какое имя участника

Ответ 1

В сообщении об ошибке может появиться другая возможность:

Тип 'Z' уже содержит определение для 'T'.

Любой отдельный тип может определять только один и тот же идентификатор один раз (перегруженные методы в сторону, поскольку они имеют параметры тоже).

В первом примере X (тип) не определяется классом Y; он определен снаружи. В то время как X (свойство) определяется классом Y.

В вашем втором примере для Z<T>, T (тип) определяется классом Z и T (свойство) также определяется классом Z. Компилятор распознает, что он создает два идентификатора одного и того же имени для одного класса, бросает руки вверх и говорит: "Nope! Nope! Nope!"

(Затем, как указывает @Rawling, команда VS IDE застряла в плохих новостях.)

Ответ 2

Возможный ответ:

Когда вы пишете не-общую версию, вы, вероятно, не можете управлять именем класса X, и люди могут ожидать, что ваше свойство будет называться X, поэтому у вас может не быть выбора.

Когда вы пишете родовую версию, никто не заботится о том, что вы назвали параметром типа, поэтому вы можете обойти это, просто выбрав другое имя.

Ergo, при написании нового родового компилятора, команда отправилась: "Мы могли бы сделать наш компилятор в состоянии выяснить, когда он имеет значение type-parameter- T, а когда он означает свойство-T, но это не реально необходимо, поскольку он может обойти его, поэтому пусть тратит время на нечто более продуктивное".

(И команда VS затем отправилась "Черт, команда компилятора оседлала нас этим, теперь нам нужно выяснить, как позволить пользователю реорганизовать это, когда он узнает, что он не может скомпилировать"...)

Ответ 3

Есть разница между типом и именем. Как бы вы написали вызов функции T, когда вы не знали (в то время или записывали код), что он был вызван?

EDIT: приведенный выше ответ неверно предполагал, что предполагаемое поведение состояло в том, что имя свойства будет меняться в зависимости от типа T.