Хобрук: Ваш путь к мастерству в программировании

Почему C # не поддерживает варианты универсальных классов?

Возьмем этот небольшой пример LINQPad:

void Main()
{
    Foo<object> foo = new Foo<string>();
    Console.WriteLine(foo.Get());
}

class Foo<out T>
{
    public T Get()
    {
        return default(T);
    }
}

Не удается скомпилировать с этой ошибкой:

Недопустимый модификатор дисперсии. В качестве варианта можно указать только параметры интерфейса и типа делегата.

Я не вижу никаких логических проблем с кодом. Все можно проверить статически. Почему этого нельзя? Вызовет ли это некоторую несогласованность в языке или это будет сочтено слишком дорогим для реализации из-за ограничения в CLR? Если последнее, что я как разработчик должен знать об указанном ограничении?

Учитывая, что интерфейсы поддерживают это, я ожидал, что поддержка классов логически вытекает из этого.


  • Это из-за ограничения среды CLR. Теперь было бы здорово, если бы вы изменили свой вопрос, чтобы кто-нибудь мог ответить, почему на это ограничение CLR. 20.03.2015
  • @Stilgar: Не могли бы вы ответить. Он не поддерживается из-за этого в базовой реализации CLR? 20.03.2015
  • Классы должны беспокоиться о деталях реализации - делегаты и интерфейсы - нет. Чтобы заставить эту работу работать, потребуется много работы. Спрашивать, почему никто из команды C # не выполнил эту работу, кажется немного бессмысленным - они этого не сделали, это не поддерживается, конец истории. 20.03.2015
  • @J ... Конечно, я читал инструкцию. Это все еще не объясняет почему потребуется много работы. Если это из-за деталей реализации, было бы неплохо узнать хотя бы немного об этой реализации. 20.03.2015
  • @J ...: Может быть, вас не интересует, как и почему, но это не значит, что каждый должен бездумно следовать руководству, не подвергая сомнению какие-либо дизайнерские решения. Хотя в целом это правда, что наиболее простой практичный ответ на вопросы вроде почему язык X не поддерживает Y? is Потому что никто не указал / не реализовал его., лучший ответ, безусловно, всегда тот, который дает объяснения, почему это не было бы такой хорошей идеей, как кажется на первый взгляд, если какие-либо такие объяснения доступны . 20.03.2015
  • Этот вопрос не имеет прямого отношения к созданию или поддержке кода. Почему разработчики выбирают, включать или не включать функцию во фреймворк или язык, напрямую не полезны при создании решения для кода (вообще). Где-а вопрос о том, как работает функция или как использовать надстройку IDE, напрямую связан с созданием решения для кода. 20.03.2015
  • Предлагаю прочитать книгу Эрика Липперта Почему не C # ...?, ответ: The answer is always the same: because no one ever designed, specified, implemented, tested, documented and shipped that feature. All six of those things are necessary to make a feature happen. All of them cost huge amounts of time, effort and money. Features are not cheap, and we try very hard to make sure that we are only shipping those features which give the best possible benefits to our users given ... 20.03.2015
  • @ O.R.Mapper Если это так, тогда вопрос должен быть What would be required for C# to support X feature, и в этом случае вопрос слишком общий и не по теме. 20.03.2015
  • @ErikPhilips Верно. Для меня Generics - это безопасность типов и написание чистого, организованного, хорошо спроектированного кода. То, что предлагает OP, является анафемой для всей философии дизайна C # - безумное приведение типов такого рода приводит к кошмарному коду. Я не понимаю, почему дизайнеры хотели поощрять подобные вещи. 20.03.2015
  • @J ... Тогда почему это имеет смысл для интерфейсов? Ваш аргумент против безумного подбора типов не имеет для меня смысла. 20.03.2015
  • @J ... я тоже, но это не имеет значения. Любой вопрос о том, почему некоторые рамки / языки не поддерживают X, на мой взгляд, не по теме, потому что это не помогает разработчику писать код больше, чем вопрос, почему в багажнике моей машины нет возможности открыть багажник из внутри влияет на мою способность водить машину. 20.03.2015
  • @ErikPhilips Это напрямую влияет на разработчиков, потому что эта функция может быть полезной. Вы можете достичь того же результата с помощью интерфейса, но если единственной причиной использования интерфейсов является поддержка дисперсии, вероятно, кто-то захочет использовать дисперсию без интерфейсов. 20.03.2015
  • Есть и другие причины, по которым никто не реализовал это, например, это может быть доказуемо невозможным. Мне очень любопытно, что нужно изменить в CLR для поддержки этого. 20.03.2015
  • @Stilgar Если это доказуемо невозможно, поэтому я задал этот вопрос, потому что я не знаю ничего на языке, что сделало бы это невозможным. Если есть, я определенно хочу знать об этом. 20.03.2015
  • @KendallFrey. Ваш вопрос Why doesn't C# support variant generic classes?, этот ответ НЕ оказывает прямого влияния на разработчиков, он НЕ оказывает никакого влияния. Если вы хотите задать вопрос по теме, это будет If c# does not support variant generic classes natively how can I emulate it. 20.03.2015
  • Вопрос имеет смысл, за исключением того, что на него можно ответить: Это ограничение среды CLR, что не очень интересно. Интересно, что это за ограничение и можно ли его снять. 20.03.2015
  • @ErikPhilips Некоторым из нас нравится понимать, что заставляет вещи работать, а не просто как заставить их работать. Если я получу какое-либо представление о C # или CLR, ответив на этот вопрос, я стану лучшим разработчиком. 20.03.2015
  • @KendallFrey Some of us like to understand what makes things tick Я согласен и делаю это сам, я запомнил aspnetwebstack.codeplex.com именно по этой причине. Я повторяю это еще раз, ответ на ваш вопрос в заголовке, скорее всего, приведет к такой причине, как я цитировал Эрика Липперта: In this particular case, the clear user benefit was in the past not large enough to justify the complications to the language which would ensue.. Этот ответ не имеет прямого преимущества для кода, он просто не имеет приоритета над другими функциями. Вместо этого спросите, как вы можете подражать этой функции. 20.03.2015
  • @ErikPhilips В этом случае ответ все равно научил бы меня, что нет серьезных препятствий. Если это так, то я могу успокоиться тем, что это просто не реализовано, и надеяться, что когда-нибудь это произойдет, вместо того, чтобы тратить время на то, чтобы самому ломать голову над этим. 20.03.2015
  • см. также: stackoverflow.com/a/5709191/327083 20.03.2015
  • и ... stackoverflow.com/a/1320710/327083 20.03.2015
  • и ... stackoverflow.com/a/9237056/327083 20.03.2015
  • @J ...: Все эти ссылки посвящены ковариации возвращаемого типа. На самом деле этот вопрос касается ковариации общего класса. 20.03.2015
  • Более подходящей ссылкой является stackoverflow.com/questions/9381507 / net-4-0-covariance /. Этот вопрос дублирует тот. 20.03.2015
  • @EricLippert Да, но поскольку ковариация типа возвращаемого значения не поддерживается в CLR, я полагаю, я имел в виду, что ковариация общего класса обязательно потребует неудобных ограничений такого рода, которых нельзя было бы ожидать от класса. 20.03.2015
  • @J ...: Я здесь не слежу за твоими мыслями. Причина отказа от поддержки ковариации типа возвращаемого значения заключается в том, что она создает неустойчивые проблемы базового класса. (Конечно, есть и другие.) Причина, по которой не следует поддерживать ковариантность универсального класса, состоит в том, что она подразумевает, что класс должен быть неизменяемым, а у нас нет хорошего способа представить это. Я не понимаю, как эти двое связаны. 20.03.2015
  • @KendallFrey: Существует серьезное препятствие для создания ковариантных универсальных классов: каждый тип Foo<T> имеет свой собственный независимый набор статических членов. Если ссылка на MyClass<SiameseCat> была приведена к MyClass<Animal>, к каким статическим членам он должен обращаться? CLR могла бы поддерживать ковариацию для общих классов, отвечающих некоторым очень строгим критериям (например, без статических членов), но определение и тестирование необходимых критериев само по себе потребовало бы много работы. 20.03.2015
  • @EricLippert Тогда мои мысли, вероятно, ошибочны. Я предполагал, что предпосылки там, где ее не было. Ваша точка зрения ясна и понятна - спасибо! 20.03.2015
  • @supercat Вау, я об этом не подумал, это хороший момент. 20.03.2015
  • @supercat: проблема несколько смягчается тем, что C # не позволяет вам вызывать статические методы из выражения экземпляра в качестве получателя. 20.03.2015

Ответы:


1

Одна из причин:

class Foo<out T>
{
  T _store;
  public T Get()
  {
    _store = default(T);
    return _store;
  }
}

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

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

С другой стороны, анализ этого намного проще:

void Main()
{
  IFoo<object> foo = new Foo<string>();
  Console.WriteLine(foo.Get());
}

interface IFoo<out T>
{
  T Get();
}

class Foo<T> : IFoo<T>
{
  T _store;
  public T Get()
  {
    _store = default(T);
    return _store;
  }
}

Легко определить, что никакая реализация IFoo<T> не нарушает ковариацию, потому что у нее ее нет. Все, что необходимо, - это убедиться, что T не используется в качестве параметра (включая параметр метода установки), и все готово.

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

Конечно, разницы достаточно, чтобы пройти точку «ну, если вы собираетесь разрешить X, было бы глупо не разрешать Y…».

20.03.2015
  • Очень хороший пример. Думаю, вы говорите, что если мой пример сработает, люди будут ожидать, что ваш пример тоже сработает? Я понимаю, как это может вызвать головную боль у языковых дизайнеров. 20.03.2015
  • да. Им придется либо позволить моему примеру работать (что является гораздо более сложным анализом, который должен учитывать уровни доступа и т. Д.), Либо красиво объяснить, почему это не так. И чем больше мы не позволяем работать, тем меньше кодировщики могут использовать эту дисперсию. Как я уже сказал, это, конечно, не было бы бесполезным (для начала это могло бы быть очень хорошо во многих неизменяемых случаях), но рентабельность определенно сильно отличается от таковой у вариантных интерфейсов. 20.03.2015

  • 2

    Класс должен содержать только параметры метода вывода (чтобы быть ковариантным) и только параметры метода ввода (чтобы быть контравариантным). Дело в том, что трудно гарантировать это для классов: например, ковариантный класс (по параметру типа T) не может иметь поля T, потому что вы можете писать в эти поля. Он отлично работал бы с действительно неизменяемыми классами, но на данный момент в C # нет всесторонней поддержки неизменяемости (скажем, как в Scala).

    20.03.2015
  • Это можно легко проверить во время компиляции, как в моем примере. 20.03.2015
  • Это не так просто гарантировать без глубокой поддержки неизменяемости в языке. См. Ссылку на отличный ответ Эрика Липперта на аналогичный вопрос. 20.03.2015
  • Неизменяемость не обязательно. Например, любое публичное поле типа T будет нарушением правил, а T должно быть инвариантным. Может быть несколько ситуаций, в которых применимо отклонение, но они все же могут существовать. 20.03.2015
  • Новые материалы

    Управление DOM для чайников вроде меня
    Одной из первых вещей, которую мы рассмотрели, когда начали изучать Javascript во Flatiron, была модель DOM. Кто он? Чем он занимается? Он больше машина, чем человек? Ну да довольно много. ДОМ..

    Что такое структура данных?
    Структура данных хранит и извлекает данные. Все, что обеспечивает эти две функции, является структурой данных . Период. Вы можете пропустить оставшуюся часть статьи, если ответ..

    мои январские чтения по программированию
    Эрик Эллиот Программирование приложения JavaScript Эл Свейгарт «Автоматизируйте скучные вещи с помощью Python» Прогрессивное веб-приложение Google..

    Создание ассоциаций секвелизации с помощью инструмента командной строки Sequelize
    Sequelize - популярный, простой в использовании инструмент объектно-реляционного сопоставления (ORM) JavaScript, который работает с базами данных SQL. Довольно просто начать новый проект с..

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

    Введение в машинное обучение для обнаружения аномалий (часть 1)
    Тщательно созданный, тщательно спроектированный ресурс для специалистов по данным. Часть 1 Главы 03 из Руководства по машинному обучению для обнаружения аномалий Внимание! Прежде чем вы..

    Начало работы с Pulumi в Digital Ocean
    Цифровой океан (ДО) — отличная альтернатива многим другим поставщикам облачных услуг. DO предоставляет простой и понятный пользовательский интерфейс, упрощающий управление инфраструктурой и..