Почему C # не делает «простой» вывод типа на дженерики? - PullRequest
4 голосов
/ 21 декабря 2010

Просто любопытно: конечно, мы все знаем, что общий случай вывода типов для дженериков неразрешим. И поэтому C # вообще не будет выполнять подтипы: если Foo является общим, Foo не является подтипом Foo или Foo или что-либо еще, что вы можете приготовить. И, конечно же, мы все решаем эту проблему с помощью уродливого интерфейса или определения абстрактных классов.

Но ... если вы не можете справиться с общей проблемой, почему бы просто не ограничить решение простыми делами. Например, в моем списке выше очевидно, что Foo является подтипом Foo , и это было бы тривиально проверить. То же самое для проверки Foo .

Так есть ли еще какой-нибудь глубокий ужас, который выползет из бездны, если они просто скажут: "Черт, мы сделаем то, что можем"? Или это просто какая-то религиозная чистота со стороны языковых парней из Microsoft?


Обновление:

Это очень старая тема. В настоящее время в C # есть var, который решает половину того, на что я жаловался, и затем, используя стиль анонимных делегатов в стиле Linq, имеет отличную нотацию для того, чтобы не вводить один и тот же материал дважды. Таким образом, каждый аспект того, против чего я возражал, был решен более поздними изменениями в C # (или, может быть, мне просто потребовалось некоторое время, чтобы узнать о вещах, которые только что были представлены, когда я опубликовал тему ...) В настоящее время в системе Isis2 есть функции для надежных облачных вычислений (isis2.codeplex.com), и я думаю, что в результате библиотека выглядит очень чисто. Проверьте это и дайте мне знать, что вы думаете). - Кен Бирман (июль 2014 года)

Ответы [ 6 ]

7 голосов
/ 21 декабря 2010

Они уже решили эту проблему во многих «простых» случаях: C # 4.0 поддерживает ковариацию и контравариантность для параметров универсального типа в интерфейсах и делегатах. Но не классы, к сожалению.

Обойти это ограничение довольно просто:

List<Foo> foos = bars.Select(bar => (Foo)bar).ToList();
5 голосов
/ 22 декабря 2010

очевидно, что Foo<int> является подтипом Foo<T>

Для вас, может быть, но не для меня.

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

Тот факт, что массивы являются ковариантнымихотя известно, что это нарушает безопасность типов, это достаточно плохо, но теперь вы хотите нарушить это для всего ?

Это относится к самой сути системы типов,Все, что делает система типов, это отклоняет программы .А из-за теоремы Райса эти отклоненные программы включают в себя программы с идеальной типизацией и безопасностью типов.

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

Первый вариант отсутствует, просто потому, что система типов C # не является мощной.достаточно, чтобы я мог выразить что-нибудь даже отдаленно интересное.Это оставляет только последнее, и оно уже находится на довольно шаткой почве из-за null, ковариантных массивов, неограниченных побочных эффектов, unsafe и так далее.Делая универсальные типы автоматически ковариантными, вы более или менее полностью убираете последний оставшийся элемент безопасности типов.

Существует только очень мало случаев, когда S <: T ⇒ G<S> <: G<T> действительно является типом-безопасный.(IEnumerable является одним из таких примеров.) И, вероятно, одинаково много случаев, когда только S <: T ⇒ G<T> <: G<S> является типобезопасным.(IObservable, IComparer, IComparable, IEqualityComparer.) Как правило, , ни G<S> <: G<T>, ни G<T> <: G<S> не являются типобезопасными.

2 голосов
/ 21 декабря 2010

Дело в том, что вы не можете сделать это для всех случаев, поэтому вы не делаете это ни для каких.Где вы рисуете линию, это проблема.Если вы не делаете это ни для кого, то каждый, кто использует C #, знает, что он этого не делает.Если вы делаете это часть времени, тогда это становится сложным.Это может стать игрой в догадки о том, как будет вести себя ваш код.Это все крайние случаи того, что легко, а что нет, становится сложным для программистов и может вызвать ошибки в коде.

Вот сценарий, который может привести к хаосу.Допустим, вы можете сделать вывод, что в сценарии А. вы можете увеличить строку. Кто-то еще приходит и изменяет часть базового типа, и это больше не соответствует действительности.Делая это либо всегда, либо никогда не применяя, вы никогда не столкнетесь с этой ситуацией, никогда.В сложной среде отслеживание подобной проблемы может быть абсолютным кошмаром, особенно если учесть, что это может быть невозможно обнаружить во время компиляции (отражение, DLR и т. Д.).Гораздо проще написать код для ручной обработки преобразования заранее, чем предположить, что он будет работать в вашем сценарии, когда существует вероятность, что когда-нибудь в конце концов он просто не будет (не считая обновления до новой версии).

C # 4.0 исправляет некоторые из них, поскольку они позволяют делать выводы о том, что они считают «безопасным» для программистов.

0 голосов
/ 23 декабря 2010

Итак, я нашел элегантный обходной путь для моей реальной проблемы (хотя я нахожу, что C # чрезмерно ограничен).

Как вы уже поняли, моя главная цель - написать код, который может регистрироватьсяобратный вызов метода, который вы напишете позже (например, в 2011 году) и который был определен извне с параметрами универсального типа, которых сегодня не существует, поэтому они недоступны мне на момент написания моей библиотеки (сегодня).

Например, мне нужно составить список объектов, которые вы определите в следующем году, и перебрать элементы списка, вызывая «Агрегат» каждого объекта."Метод. Я разочарован тем, что, хотя вы, безусловно, можете зарегистрировать свой класс или объект в своем классе, проверка типов C # не позволяла мне вызывать методы, потому что я не знаю типы (я могу получить типы через GetType (), но не могу использовать их в качестве типов в выражениях.) Поэтому я начал использовать рефлексию, чтобы вручную скомпилировать желаемое поведение, которое уродливо и может нарушать безопасность типов.

И ответ ...анонимные классы. Поскольку анонимный класс похож на замыкание, я могу определить свои обратные вызовы внутри подпрограммы «register» (которая имеет доступ к типам: вы можете передавать их при регистрации ваших объектов). Я создам немного анонимногометод обратного вызова, прямо в строке, и сохраните в нем делегата. Метод обратного вызова может вызвать ваш агрегатор, так как C # считает, что он «знает» типы параметров. И все же мой список может перечислять методы с известными сигнатурами типов во время компиляции библиотекиа именно завтра.

Очень чисто и C # не заканчивает тем, что сводит меня с ума. Тем не менее, C # rна самом деле не старается изо всех сил вывести отношения подтипа (извините, Эрик: «что должно быть отношениями подтипа»).Дженерики не являются макросами в C. Но C # слишком близок к тому, чтобы рассматривать их, как если бы они были!

И это отвечает на мой собственный (настоящий) вопрос.

0 голосов
/ 22 декабря 2010

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

class Base<T> 
{
 public T Method() { return default(T);}
}

Если вы предложите, чтобы Base и Base былиоба имеют «Метод» с одинаковой сигнатурой (исходящий из базового класса), и его можно вызвать с помощью ссылки на базовый класс.Не могли бы вы объяснить, какое очевидное возвращаемое значение «Метод» будет иметь в вашей системе, если указать на объект Base<int> или Base<string>, указав на базовый класс?

0 голосов
/ 21 декабря 2010

Действительно хорошим примером из реальной жизни языка, который запутан и сделан более сложным, чем это необходимо в особых случаях, является английский.В языке программирования такие вещи, как «я до е, за исключением после с», делают язык более сложным в использовании и, как таковой, менее полезным.

...