При программировании на C # я наткнулся на странное решение о дизайне языка, которое просто не могу понять.
Таким образом, C # (и CLR) имеют два совокупных типа данных: struct
(тип значения, хранящийся в стеке, без наследования) и class
(тип ссылки, хранящийся в куче, имеет наследование).
Вначале эта настройка звучит неплохо, но затем вы наткнулись на метод, принимающий параметр агрегатного типа, и чтобы выяснить, действительно ли он имеет тип значения или ссылочный тип, вы должны найти объявление его типа. Это может стать действительно запутанным время от времени.
Похоже, что общепринятым решением проблемы является объявление всех struct
s как «неизменных» (установка их полей в readonly
), чтобы предотвратить возможные ошибки, ограничивая struct
полезность s.
C ++, например, использует гораздо более удобную модель: он позволяет вам создавать экземпляр объекта либо в стеке, либо в куче и передавать его по значению или по ссылке (или по указателю). Я продолжаю слышать, что C # был вдохновлен C ++, и я просто не могу понять, почему он не взял эту единственную технику. Объединение class
и struct
в одну конструкцию с двумя различными вариантами размещения (куча и стек) и передача их в виде значений или (явно) в качестве ссылок через ключевые слова ref
и и out
выглядит приятной вещью.
Вопрос в том, почему class
и struct
стали отдельными понятиями в C # и CLR вместо одного агрегатного типа с двумя вариантами размещения?
источник
struct
s не всегда хранятся в стеке; Рассмотрим объект сstruct
полем. Кроме того, как отметил Мейсон Уилер, проблема с нарезкой, вероятно, является главной причиной.Ответы:
Причина того, что C # (и Java, и, в сущности, любой другой язык OO, разработанный после C ++) не копировали модель C ++ в этом аспекте, заключается в том, что в C ++ это ужасный беспорядок.
Вы правильно определили соответствующие пункты выше:
struct
тип значения, без наследования.class
: ссылочный тип, имеет наследование. Типы наследования и значения (или, более конкретно, полиморфизм и передача по значению) не смешиваются; если вы передаете объект типаDerived
аргументу метода типаBase
, а затем вызываете для него виртуальный метод, единственный способ получить правильное поведение - убедиться, что полученное значение является ссылкой.Между этим и всеми другими беспорядками, с которыми вы сталкиваетесь в C ++, имея наследуемые объекты в качестве типов значений ( приходят на ум конструкторы копирования и нарезка объектов !), Лучшим решением является Just Say No.
Хороший языковой дизайн - это не только реализация функций, но и знание того, какие функции не следует реализовывать, и один из лучших способов сделать это - учиться на ошибках тех, кто был до вас.
источник
По аналогии, C # в основном похож на набор инструментов механика, где кто-то прочитал, что обычно следует избегать плоскогубцы и разводные ключи, поэтому он вообще не включает разводные ключи, а плоскогубцы запираются в специальном ящике с надписью «небезопасно» и может быть использован только с разрешения супервайзера после подписания заявления об отказе от ответственности, которое освобождает вашего работодателя от ответственности за ваше здоровье.
C ++, для сравнения, включает не только разводные ключи и плоскогубцы, но и некоторые довольно необычные инструменты специального назначения, назначение которых не сразу понятно, и если вы не знаете, как правильно их держать, они могут легко отрезать вам большой палец (но как только вы поймете, как их использовать, можете делать вещи, которые по сути невозможны с помощью основных инструментов в наборе инструментов C #). Кроме того, у него есть токарный станок, фрезерный станок, плоскошлифовальный станок, ленточная пила для резки металла и т. Д., Позволяющие проектировать и создавать совершенно новые инструменты в любое время, когда вы чувствуете необходимость (но да, инструменты этих машинистов могут и будут вызывать серьезные травмы, если вы не знаете, что вы делаете с ними - или даже если вы просто неосторожны).
Это отражает основное различие в философии: C ++ пытается предоставить вам все инструменты, которые вам могут понадобиться, по существу, для любого дизайна, который вам может понадобиться. Он практически не пытается контролировать использование этих инструментов, поэтому их легко использовать для создания проектов, которые хорошо работают только в редких ситуациях, а также проектов, которые, вероятно, являются просто паршивой идеей, и никто не знает о ситуации, в которой они, скорее всего, будут хорошо работать. В частности, многое из этого достигается путем разделения дизайнерских решений - даже тех, которые на практике действительно почти всегда связаны. В результате между написанием C ++ и написанием C ++ существует огромная разница. Чтобы хорошо писать на C ++, вам нужно знать много идиом и практических правил (включая практические правила о том, как серьезно пересмотреть, прежде чем нарушать другие эмпирические правила). В результате, C ++ больше ориентирован на простоту использования (экспертами), чем на простоту обучения. Есть также (слишком много) обстоятельства, при которых это не очень просто использовать.
C # делает гораздо больше, чтобы попытаться (или, по крайней мере, чрезвычайно убедительно) предложить то, что разработчики языка считают хорошими практиками проектирования. Многие вещи, которые в C ++ отделены (но обычно на практике встречаются вместе), напрямую связаны в C #. Это позволяет «небезопасному» коду немного расширять границы, но, честно говоря, не очень.
В результате, с одной стороны, существует довольно много дизайнов, которые можно выразить довольно прямо в C ++, которые существенно неуклюжи для выражения в C #. С другой стороны, это целое намного легче выучить C #, и шансы получения действительно ужасный дизайн , который не будет работать для вашей ситуации (или , возможно , любой другой) являются резко снижается. Во многих (возможно, даже в большинстве) случаях вы можете получить надежный, работоспособный дизайн, просто, так сказать, «плывя по течению». Или, поскольку один из моих друзей (по крайней мере, мне нравится думать о нем как о друге - не уверен, действительно ли он согласен) любит это выражать, C # позволяет легко попасть в пропасть успеха.
Итак, более подробно рассмотрев вопрос о том, как
class
иstruct
как они появились на двух языках: объектах, созданных в иерархии наследования, где вы можете использовать объект производного класса под видом его базового класса / интерфейса, вы в значительной степени застряв в том факте, что вам обычно нужно делать это с помощью какого-то указателя или ссылки - на конкретном уровне происходит то, что объект производного класса содержит что-то в памяти, что можно рассматривать как экземпляр базового класса / интерфейс, и производным объектом манипулируют через адрес этой части памяти.В C ++ программист должен сделать это правильно - когда он использует наследование, он должен убедиться, что (например) функция, которая работает с полиморфными классами в иерархии, делает это с помощью указателя или ссылки на базу учебный класс.
В C # то, что по сути такое же разделение между типами, гораздо более явное и обеспечивается самим языком. Программисту не нужно предпринимать никаких шагов для передачи экземпляра класса по ссылке, потому что это произойдет по умолчанию.
источник
Это из "C #: зачем нам нужен другой язык?" - Ганнерсон, Эрик:
Ссылочная семантика для объектов - это способ избежать многих неприятностей (конечно, и не только нарезки объектов), но проблемы реального мира могут иногда требовать объектов со смысловой семантикой (например, взглянуть на звуки, как будто я никогда не должен использовать ссылочную семантику, верно? для другой точки зрения).
Следовательно, какой лучший подход, чтобы отделить эти грязные, уродливые и плохие объекты со значением-семантикой под тегом
struct
?источник
int[]
должна ли переменная типа быть разделяемой или изменяемой (массивы могут быть и другими, но обычно не обоими), поможет неправильному коду выглядеть неправильно.Вместо того, чтобы думать о типах значений, происходящих из
Object
, было бы более полезно подумать о типах хранилищ, существующих в совершенно отдельном юниверсе от типов экземпляров классов, но для каждого типа значений должен быть соответствующий тип объекта кучи. Место хранения структурного типа просто содержит объединение открытых и закрытых полей типа, а тип кучи генерируется автоматически в соответствии с шаблоном:и для такого выражения, как: Console.WriteLine («Значение равно {0}», somePoint);
переводится как: boxed_Point box1 = новый boxed_Point (somePoint); Console.WriteLine («Значение равно {0}», box1);
На практике, поскольку типы мест хранения и типы экземпляров кучи существуют в отдельных юниверсах, нет необходимости называть типы экземпляров кучи такими, как
boxed_Int32
; поскольку система будет знать, какие контексты требуют экземпляра объекта кучи, а какие - места хранения.Некоторые люди думают, что любые типы значений, которые не ведут себя как объекты, следует считать «злыми». Я придерживаюсь противоположной точки зрения: поскольку места хранения типов значений не являются ни объектами, ни ссылками на объекты, ожидание того, что они должны вести себя как объекты, следует считать бесполезным. В тех случаях, когда структура может вести себя как объект, нет ничего плохого в том, чтобы один из них делал это, но в основе каждого
struct
лежит не что иное, как совокупность открытых и закрытых полей, склеенных клейкой лентой.источник