Что такое «особый класс»?

114

После неудачной попытки скомпилировать что-то вроде следующего:

public class Gen<T> where T : System.Array
{
}

с ошибкой

Ограничение не может быть специальным классом System.Array.

Я начал задаваться вопросом , что именно это «особый класс»?

Кажется, что люди часто получают такую ​​же ошибку, когда задают System.Enumобщее ограничение. Я получил те же результаты , с System.Object, System.Delegate, System.MulticastDelegateи System.ValueTypeтоже.

Их больше? Я не могу найти никакой информации о "специальных классах" в C #.

Кроме того , что это так особенного тех классов , которые мы не можем использовать их в качестве универсального типа ограничения?

Mints97
источник
14
Не думаю, что это прямой дубликат. Вопрос не в том, «почему я не могу использовать это как ограничение», а в том, «что это за специальные классы». Я рассмотрел эти вопросы, и они просто заявляют, почему его было бы бесполезно использовать в качестве ограничения, не объясняя, что на самом деле представляет собой «специальный класс» и почему он считается особенным.
Адам Хоулдсворт
2
По моему опыту, классы, которые используются, но вы не можете использовать их напрямую, только неявно через другой синтаксис, являются специальными классами. Enum попадает в ту же категорию. Что именно делает их особенными, я не знаю.
Лассе В. Карлсен
@AndyKorneyev: это совсем другой вопрос. Я прошу дать определение «особого класса» и / или их исчерпывающий список. В этом вопросе просто спрашивается, почему System.Array не может быть ограничением общего типа.
Mints97
В документации говорится: «[...] только система и компиляторы могут явно наследовать от класса Array.». Вероятно, это то, что делает его особенным классом - компилятор специально обрабатывает его.
РБ.
1
@RB .: неправильно. Эта логика означала бы, что System.Objectэто не «особый класс», поскольку это действительно:, public class X : System.Object { }но System.Objectвсе же «особый класс».
Mints97

Ответы:

106

Из исходного кода Roslyn это выглядит как список жестко запрограммированных типов:

switch (type.SpecialType)
{
    case SpecialType.System_Object:
    case SpecialType.System_ValueType:
    case SpecialType.System_Enum:
    case SpecialType.System_Delegate:
    case SpecialType.System_MulticastDelegate:
    case SpecialType.System_Array:
        // "Constraint cannot be special class '{0}'"
        Error(diagnostics, ErrorCode.ERR_SpecialTypeAsBound, syntax, type);
        return false;
}

Источник: Binder_Constraints.cs IsValidConstraintType
Я нашел его с помощью поиска GitHub: «Ограничение не может быть специальным классом»

Коби
источник
1
@kobi 702 становится ошибкой компилятора CS0702, как видно из выходных данных компилятора (которые этот вопрос не цитирует) и других ответов.
AakashM
1
@AakashM - Спасибо! Я попытался скомпилировать и по какой-то причине не получил номер ошибки. Затем мне потребовалось почти 5 минут, чтобы выяснить это, и не было времени, чтобы отредактировать свой комментарий. Печальная история.
Коби
1
@Kobi: вы должны посмотреть в окно вывода , там вы найдете точный номер кода ошибки компилятора CS0702.
Тим Шмелтер
9
Итак, теперь реальный вопрос: почему эти особые классы?
Дэвид говорит: "Восстановите Монику"
@DavidGrinberg Может быть, причина в том, что вы не можете наследовать от этих типов напрямую (кроме object), или, по крайней мере, это как-то связано с этим. Также where T : Arrayпозволит передать Assay как T, что, вероятно, не то, чего хочет большинство людей.
IllidanS4 хочет вернуть Монику
42

Я нашел комментарий Джона Скита от 2008 года по аналогичному вопросу: почему System.Enumограничение не поддерживается.

Я знаю, что это немного не по теме , но он спросил об этом Эрика Липперта (команда C #), и они дали такой ответ:

Во-первых, ваша догадка верна; ограничения на ограничения в основном являются артефактами языка, а не CLR. (Если бы мы реализовали эти функции, мы бы хотели изменить несколько незначительных вещей в CLR относительно того, как указываются перечисляемые типы, но в основном это будет языковая работа.)

Во-вторых, мне лично хотелось бы иметь ограничения делегата, ограничения перечисления и возможность указывать ограничения, которые сегодня недопустимы, потому что компилятор пытается спасти вас от вас самих. (То есть сделать закрытые типы законными как ограничения и т. Д.)

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

Амир Попович
источник
10
@YuvalItzchakov - лучше цитировать Github \ MSDN? Команда C # дала конкретный ответ относительно проблемы или аналогичной проблемы ... Это никому не может навредить. Джон Скит только что процитировал их, и это довольно надежно, когда дело доходит до C # ..
Амир Попович
5
Не нужно расстраиваться. Я не имел в виду, что это неверный ответ :) Просто поделился своими мыслями о фундаменте, которым является jonskeet; p
Юваль Ицчаков
40
FYI BTW Я думаю, что вы цитируете меня. :-)
Эрик Липперт
2
@EricLippert - Это делает цитату еще более надежной.
Амир Попович
Домен ссылки в ответе мертв.
Pang
25

Согласно MSDN это статический список классов:

Ошибка компилятора CS0702

Ограничение не может быть идентификатором специального класса. Следующие типы не могут использоваться в качестве ограничений:

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Тим Шмельтер
источник
4
Круто, вроде ответ правильный, хорошая находка! Но где System.MulticastDelegateв списке?
Mints97
8
@ Mints97: понятия не имею, возможно, отсутствие документации?
Тим Шмелтер
Похоже, вы тоже не можете наследовать от этих классов.
Дэвид Клемпфнер
14

В соответствии со Спецификацией языка C # 4.0 (Закодировано: [10.1.5] Ограничения параметров типа) говорится о двух вещах:

1] Тип не должен быть объектом. Поскольку все типы являются производными от объекта, такое ограничение не имело бы никакого эффекта, если бы оно было разрешено.

2] Если T не имеет первичных ограничений или ограничений параметра типа, его эффективным базовым классом является object.

Когда вы определяете универсальный класс, вы можете применять ограничения к типам типов, которые клиентский код может использовать для аргументов типа при создании экземпляра вашего класса. Если клиентский код пытается создать экземпляр вашего класса, используя тип, который не разрешен ограничением, результатом будет ошибка времени компиляции. Эти ограничения называются ограничениями. Ограничения указываются с помощью контекстного ключевого слова where. Если вы хотите ограничить универсальный тип ссылочным типом, используйте: class.

public class Gen<T> where T : class
{
}

Это запретит универсальному типу быть типом значения, таким как int или struct и т. Д.

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

  • System.Object
  • System.Array
  • System.Delegate
  • System.Enum
  • System.ValueType.
Рахул Никате
источник
12

В Framework есть определенные классы, которые эффективно передают особые характеристики всем производным от них типам, но сами не обладают этими характеристиками. . Сама среда CLR не налагает запрета на использование этих классов в качестве ограничений, но связанные с ними универсальные типы не будут приобретать ненаследуемые характеристики, как конкретные типы. Создатели C # решили, что, поскольку такое поведение может сбить с толку некоторых людей, и они не видели в нем какой-либо полезности, им следует запретить такие ограничения, а не позволить им вести себя так, как они делают в среде CLR.

Если, например, разрешено писать void CopyArray<T>(T dest, T source, int start, int count):; можно было бы передать destи sourceметодам, которые ожидают аргумент типа System.Array; кроме того, можно было бы получить проверку во время компиляции, которые destи sourceбыли совместимыми типами массивов, но нельзя было бы получить доступ к элементам массива с помощью[] оператора.

Невозможность использования Arrayв качестве ограничения в большинстве случаев довольно легко обойти, поскольку void CopyArray<T>(T[] dest, T[] source, int start, int count)будет работать почти во всех ситуациях, когда будет работать первый метод. Однако у него есть слабость: первый метод будет работать в сценарии, в котором один или оба аргумента имеют тип, System.Arrayпри этом отклоняются случаи, когда аргументы являются несовместимыми типами массивов; добавление перегрузки, в которой оба аргумента имеют тип System.Array, заставит код принимать дополнительные случаи, которые он должен принимать, но также заставит его ошибочно принимать случаи, которые он не должен принимать.

Я считаю решение объявить вне закона большинство специальных ограничений утомительным. Единственным, у которого было бы нулевое семантическое значение, было бы System.Object[поскольку, если бы это было законным как ограничение, ему удовлетворяло бы что угодно]. System.ValueTypeвероятно, было бы не очень полезно, поскольку ссылки типа на ValueTypeсамом деле не имеют много общего с типами значений, но они могут иметь какое-то значение в случаях, связанных с отражением. Оба System.Enumи System.Delegateбудут иметь какое-то реальное применение, но поскольку создатели C # не думали о них, они объявлены вне закона без уважительной причины.

Supercat
источник
10

Следующее можно найти в CLR через C # 4th Edition:

Основные ограничения

Параметр типа может указывать ноль основных ограничений или одно основное ограничение. Первичное ограничение может быть ссылочным типом, который идентифицирует незапечатанный класс. Вы не можете указать один из следующих специальных типов ссылок: System.Object , System.Array , System.Delegate , System.MulticastDelegate , System.ValueType , System.Enum или System.Void . При указании ограничения ссылочного типа вы обещаете компилятору, что указанный аргумент типа будет либо одного типа, либо типа, производного от типа ограничения.

Клаудио П
источник
Смотрите также: C # LS раздел 10.1.4.1: Непосредственный базовый класс типа класса не должен быть каким - либо из следующих типов: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, или System.ValueType. Более того, объявление универсального класса нельзя использовать System.Attributeкак прямой или косвенный базовый класс.
Йерун Ванневель
5

Я не думаю, что существует какое-то официальное определение «особых классов» / «особых типов».

Вы можете думать о них как о типах, которые нельзя использовать с семантикой «обычных» типов:

  • вы не можете создать их напрямую;
  • вы не можете наследовать от них пользовательский тип напрямую;
  • есть некоторая магия компилятора для работы с ними (опционально);
  • прямое использование их экземпляров по крайней мере бесполезно (необязательно; представьте, что вы создали общий код выше, какой общий код вы собираетесь написать?)

PS Добавлю System.Voidв список.

Деннис
источник
2
System.Voidдает совершенно другую ошибку при использовании в качестве общего ограничения =)
Mints97
@ Mints97: правда. Но если речь идет об «особенном», то да, voidочень особенном. :)
Деннис
@Dennis: код, который имеет пару параметров ограниченного типа, System.Arrayможет использовать такие методы, как Array.Copyперемещение данных от одного к другому; код с параметрами ограниченного типа System.Delegateсможет использовать Delegate.Combineих и привести результат к правильному типу . Эффективное использование общего известного типа Enumбудет использовать Reflection один раз для каждого такого типа, но универсальный HasAnyFlagметод может быть в 10 раз быстрее, чем неуниверсальный метод.
supercat