Почему все классы в .NET глобально наследуются от класса Object?

16

Мне очень интересно, какие преимущества дает подход «глобального корневого класса» для фреймворка. Проще говоря, по каким причинам .NET Framework был разработан, чтобы иметь один класс корневых объектов с общей функциональностью, подходящей для всех классов.

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

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

Итак, мне очень интересно узнать, по каким причинам действительно .NET-архитекторы толкают к разработке фреймворка таким образом.

Антон Семенов
источник
6
Помните, что .NET Framework является частью обобщенной среды разработки программного обеспечения. Более конкретным фреймворкам, таким как ваша, может не понадобиться «корневой» объект. objectОтчасти в фреймворке .NET есть корень, потому что он предоставляет некоторые базовые возможности для всех объектов, таких как ToString()иGetHashCode()
Роберт Харви,
10
«Мне очень интересно узнать, по каким причинам архитекторы .NET действительно так спроектировали фреймворк». Для этого есть три веские причины: 1. Java сделала это таким образом, 2. Java сделала это таким образом, и 3. Java сделала это таким образом.
dasblinkenlight
2
@vcsjones: а Java например уже загрязнена с wait()/ notify()/ notifyAll()и clone().
Иоахим Зауэр
3
@daskblinkenlight Это пу. Ява так не делала.
Данте
2
@dasblinkenlight: К вашему сведению, в настоящее время Java копирует C # с лямбдами, функциями LINQ и т. д.
user541686

Ответы:

18

Наиболее неотложной причиной для этого Objectявляются контейнеры (до генериков), которые могут содержать что угодно, вместо того, чтобы идти в стиле C: «Напишите это снова для всего, что вам нужно». Конечно, можно утверждать, что идея о том, что все должно наследоваться от определенного класса, а затем злоупотреблять этим фактом, чтобы полностью потерять каждую частичку безопасности типов, настолько ужасна, что это должен был быть гигантский предупреждающий сигнал об отправке языка без обобщений, а также означает, что это Objectполностью избыточно для нового кода.

DeadMG
источник
6
Это правильный ответ. Отсутствие общей поддержки было единственной причиной. Другие аргументы «общих методов» не являются реальными аргументами, потому что общие методы не должны быть общими - Пример: 1) ToString: злоупотребляет в большинстве случаев; 2) GetHashCode: в зависимости от того, что делает программа, большинству классов этот метод не нужен; 3) GetType: не должен быть методом экземпляра
Codism
2
Каким образом .NET «полностью теряет каждую частичку безопасности типов», «злоупотребляя» идеей, что все должно наследоваться от определенного класса? Есть ли какой-нибудь дерьмовый код, который может быть написан вокруг, Objectкоторый не может быть написан void *в C ++?
Carson63000
3
Кто сказал, что я думаю, что void*лучше? Это не так. Если что-нибудь. это даже хуже .
DeadMG
void*хуже в C со всеми неявными приведениями указателей, но C ++ более строг в этом отношении. По крайней мере, вы должны бросить явно.
Тамас Селеи
2
@fish: Терминологический спор: в C нет такого понятия, как «неявное приведение». Приведение - это явный оператор, который определяет преобразование. Вы имеете в виду «неявные преобразования указателей ».
Кит Томпсон
11

Я помню, как Эрик Липперт однажды сказал, что наследование от System.Objectкласса обеспечило «лучшую ценность для клиента». [править: да, он сказал это здесь ]:

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

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

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

Наличие всего, что происходит от System.Objectкласса, обеспечивает надежность и полезность, которую я часто уважаю. Я знаю, что все объекты будут иметь тип ( GetType), что они будут соответствовать методам финализации CLR ( Finalize), и что я могу использовать GetHashCodeих при работе с коллекциями.

gws2
источник
2
Это ошибочно. Вам не нужно GetType, вы можете просто расширить, typeofчтобы заменить его функциональность. Что касается Finalize, то на самом деле, многие типы совсем не являются финализируемыми и не имеют значимого метода Finalize. Кроме того, многие типы не могут быть хэшируемыми или конвертируемыми в строковые, особенно внутренние типы, которые никогда не предназначены для таких вещей и никогда не передаются для публичного использования. Все эти типы имеют свои интерфейсы без необходимости загрязнены.
DeadMG
5
Нет, это не недостатки - я никогда не утверждал , что вы будете использовать GetType, Finalizeили GetHashCodeдля каждого System.Object. Я просто заявил, что они были там, если вы хотели их. Что касается «ненужного загрязнения их интерфейсов», я ссылаюсь на мою оригинальную цитату из elippert: «лучшая ценность для клиента». Очевидно, что команда .NET была готова иметь дело с компромиссами.
gws2
Если это не нужно, то это загрязнение интерфейса. «Если вы хотите» никогда не является веской причиной для включения метода. Он должен быть там только потому, что он вам нужен, и ваши покупатели назовут его . Иначе это не хорошо. Кроме того, ваша цитата из Липперта является призывом к заблуждению авторитета, поскольку он также не говорит ничего, кроме «Это хорошо».
DeadMG
Я не пытаюсь аргументировать вашу точку зрения @DeadMG - вопрос был конкретно «Почему все классы в .NET глобально наследуются от класса Object», и поэтому я связался с предыдущим постом кем-то, очень близким к команде .NET в Microsoft, которая дала законный, хотя и неясный, ответ на вопрос, почему команда разработчиков .NET выбрала такой подход.
gws2
Слишком расплывчато, чтобы быть ответом на этот вопрос.
DeadMG
4

Я думаю, что разработчики Java и C # добавили корневой объект, потому что он был для них по сути бесплатным, как в бесплатном обеде .

Затраты на добавление корневого объекта во многом равны затратам на добавление вашей первой виртуальной функции. Поскольку CLR и JVM являются средами для сборки мусора с финализаторами объектов, вам необходимо иметь хотя бы одну виртуальную среду для вашего java.lang.Object.finalizeили для вашего System.Object.Finalizeметода. Следовательно, затраты на добавление корневого объекта уже оплачены, вы можете получить все преимущества, не оплачивая его. Это лучшее из обоих миров: пользователи, которым нужен общий корневой класс, получают то, что хотят, а пользователи, которым все равно, могут программировать так, как если бы их не было.

dasblinkenlight
источник
4

Мне кажется, у вас есть три вопроса: один - почему общий корень был введен в .NET, второй - каковы его преимущества и недостатки, а третий - хорошая ли идея, чтобы элемент инфраструктуры имел глобальный корень.

Почему .NET имеет общий корень?

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

Кроме того, насколько мне известно, наличие общего корня с базовыми методами, такими как equals()и, hashCode()было воспринято очень положительно в Java, и на C # оказала влияние (среди прочего) Java, поэтому они также хотели иметь эту функцию.

Каковы преимущества и недостатки наличия общего корня в языке?

Преимущество наличия общего корня:

  • Вы можете разрешить всем объектам иметь определенную функциональность, которую вы считаете важной, например, equals()и hashCode(). Это означает, например, что каждый объект в Java или C # может использоваться в хэш-карте - сравните эту ситуацию с C ++.
  • Вы можете ссылаться на объекты неизвестных типов - например, если вы просто передаете информацию, как это делали предварительные контейнеры.
  • Вы можете ссылаться на объекты непознаваемого типа - например, при использовании отражения.
  • Он может использоваться в тех редких случаях, когда вы хотите иметь возможность принимать объект, который может иметь разные и другие несвязанные типы - например, в качестве параметра printf-подобного метода.
  • Это дает вам гибкость в изменении поведения всех объектов путем изменения только одного класса. Например, если вы хотите добавить метод ко всем объектам в вашей программе на C #, вы можете добавить метод расширения object. Возможно, не очень часто, но без общего корня это было бы невозможно.

Недостаток наличия общего корня:

  • Можно злоупотреблять ссылкой на объект некоторого значения, даже если вы могли бы использовать для него более точный тип.

Должны ли вы идти с общим рутом в вашем фреймворке?

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

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

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

дуб
источник
Я думаю, что C # был не просто под влиянием Java, он в какой-то момент был Java. Microsoft больше не могла использовать Java, поэтому потеряла бы миллиарды инвестиций. Они начали с того, что дали язык, на котором у них уже было новое имя.
Майк Браун
3

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

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

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

Но так бывает. Если это так, то продолжайте абстрагироваться от этого общего поведения.

Telastyn
источник
2

Это подтолкнуло к корневому объекту, потому что они хотели, чтобы все классы в структуре поддерживали определенные вещи (получение хеш-кода, преобразование в строку, проверки на равенство и т. Д.). И C #, и Java сочли полезным поместить все эти общие свойства объекта в некоторый корневой класс.

Имейте в виду, что они не нарушают никаких принципов ООП или чего-либо еще. Все в классе Object имеет смысл в любом подклассе (читай: любой класс) в системе. Если вы решили принять этот дизайн, убедитесь, что вы следуете этому шаблону. То есть не включайте в корень вещи, которые не принадлежат каждому отдельному классу в вашей системе. Если вы будете тщательно следовать этому правилу, я не вижу причин, по которым у вас не должно быть корневого класса, содержащего полезный общий код для всей системы.

Oleksi
источник
2
Это совсем не так. Существует много внутренних классов, которые никогда не хэшируются, не отражаются и не преобразуются в строку. Ненужные наследование и лишние методы определенно делают нарушают многие принципы.
DeadMG
2

Пара причин, общая функциональность, которой могут поделиться все дочерние классы. И это также дало авторам фреймворка возможность записывать множество других функций в фреймворк, который мог использовать каждый. Примером может служить кэширование и сессии ASP.NET. В них можно хранить практически все, потому что они написали методы add для принятия объектов.

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

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

Несколько лет назад было очень большое приложение, где я создал корневой класс. После нескольких месяцев разработки он был заполнен кодом, в котором не было бизнеса. Мы конвертировали старое приложение ASP, и корневой класс был заменой старого файла global.inc, который мы использовали в прошлом. Пришлось усвоить этот урок нелегким путем.

bwalk2895
источник
2

Все автономные объекты кучи наследуются от Object; это имеет смысл, потому что все автономные объекты кучи должны иметь определенные общие аспекты, такие как средства идентификации их типа. В противном случае, если сборщик мусора имеет ссылку на объект кучи неизвестного типа, он не сможет узнать, какие биты в блоке памяти, связанном с этим объектом, следует рассматривать как ссылки на другие объекты кучи.

Кроме того, в системе типов удобно использовать один и тот же механизм для определения членов структур и членов классов. Поведение мест хранения типов значений (переменных, параметров, полей, слотов массивов и т. Д.) Очень отличается от поведения мест хранения классов, но такие поведенческие различия достигаются в компиляторах исходного кода и механизме выполнения (включая компилятор JIT), а не выражается в системе типов.

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

Все это важно, потому что ссылки на объекты кучи типа значения ведут себя как ссылки на классы, а не как типы значений. Рассмотрим, например, следующий код:

string testEnumerator <T> (T it) где T: IEnumerator <string>
{
  var it2 = это;
  it.MoveNext ();
  it2.MoveNext ();
  вернуть его. Текущий;
}
публичный тест void ()
{
  var theList = new List <string> ();
  theList.Add ( "Fred");
  theList.Add ( "Джордж");
  theList.Add ( "Перси");
  theList.Add ( "Молли");
  theList.Add ( "Рон");

  var enum1 = theList.GetEnumerator ();
  IEnumerator <string> enum2 = enum1;

  Debug.Print (testEnumerator (enum1));
  Debug.Print (testEnumerator (enum1));
  Debug.Print (testEnumerator (enum2));
  Debug.Print (testEnumerator (enum2));
}

Если testEnumerator()метод передается в хранилище типа значения, itон получит экземпляр, чьи открытые и закрытые поля будут скопированы из переданного значения. Локальная переменная it2будет содержать другой экземпляр, все поля которого скопированы it. Вызов MoveNextна it2не повлияет it.

Если приведенному выше коду передается место хранения типа класса, то переданное значение itи it2все будут ссылаться на один и тот же объект, и, таким образом, вызов MoveNext()любого из них будет эффективно вызывать его для всех них.

Обратите внимание, что приведение List<String>.Enumeratorк IEnumerator<String>эффективно превращает его из типа значения в тип класса. Тип объекта кучи есть, List<String>.Enumeratorно его поведение будет сильно отличаться от типа значения с тем же именем.

Supercat
источник
+1 за не упоминание ToString ()
JeffO
1
Это все о деталях реализации. Нет необходимости выставлять их пользователю.
DeadMG
@DeadMG: Что ты имеешь в виду? Наблюдаемое поведение a, List<T>.Enumeratorкоторое хранится как a List<T>.Enumerator, существенно отличается от поведения того, которое хранится в a IEnumerator<T>, в том, что сохранение значения предыдущего типа в месте хранения любого типа сделает копию состояния перечислителя, при сохранении значения последнего типа в хранилище, которое также относится к последнему типу, копирование не производится.
суперкат
Да, но не Objectимеет ничего общего с этим фактом.
DeadMG
@DeadMG: Моя точка зрения такова, что экземпляр кучи типа System.Int32также является экземпляром System.Object, но место хранения типа не System.Int32содержит ни System.Objectэкземпляра, ни ссылки на него.
Суперкат
2

Этот дизайн действительно восходит к Smalltalk, который я бы рассматривал в основном как попытку добиться ориентации объекта за счет почти любых других проблем. Как таковой, он имеет тенденцию (на мой взгляд) использовать объектную ориентацию, даже когда другие методы, вероятно (или даже наверняка) превосходят.

Наличие единственной иерархии с Object(или чем-то похожим) в корне позволяет довольно легко (для одного примера) создавать классы коллекций в виде коллекций Object, поэтому тривиально для коллекции содержать любой тип объекта.

В обмен на это довольно незначительное преимущество вы получаете целый ряд недостатков. Во-первых, с точки зрения дизайна вы получите действительно безумные идеи. По крайней мере, согласно взгляду Вселенной на Java, что общего между Атеизмом и Лесом? Что у них обоих есть хэш-коды! Является ли карта коллекцией? По словам Java, нет, это не так!

В 70-х, когда разрабатывался Smalltalk, такая чепуха была принята, прежде всего потому, что никто не придумал разумной альтернативы. Smalltalk был завершен в 1980 году, а к 1983 году была разработана Ada (которая включает в себя дженерики). Хотя Ада так и не достигла той популярности, которую некоторые предсказывали, ее обобщений было достаточно для поддержки коллекций объектов произвольных типов - без безумия, присущего монолитным иерархиям.

При разработке Java (и, в меньшей степени, .NET) монолитная иерархия классов, вероятно, рассматривалась как «безопасный» выбор - с проблемами, но в основном с известными проблемами. Родовое программирование, напротив, было тем, что почти все (даже тогда) поняли, было, по крайней мере, теоретически гораздо лучшим подходом к проблеме, но тот, который многие коммерчески ориентированные разработчики считали довольно плохо изученным и / или рискованным (то есть в коммерческом мире). Ада была в основном отклонена как провал).

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

Однако для нового дизайна сегодня нет разумного вопроса: использование монолитной иерархии - явная ошибка и плохая идея.

Джерри Гроб
источник
Стоит отметить, что шаблоны, как было известно, были замечательными и полезными до того, как были поставлены .NET.
DeadMG
В коммерческом мире, Ада была неудача, не столько из - за его многословие, но из - за отсутствия стандартизированных интерфейсов , работающих системных служб. Отсутствие стандартного API для файловой системы, графики, сети и т. Д. Сделало разработку Ada «размещенных» приложений чрезвычайно дорогой.
Кевин Клайн
@kevincline: Вероятно, мне следовало бы сказать, что это немного по-другому - чтобы Ада в целом была отклонена как провал из-за ее провала на рынке. Отказ от использования Ады был (ИМО) вполне разумным - но отказываться учиться у него гораздо реже (в лучшем случае).
Джерри Гроб
@Jerry: Я думаю, что шаблоны C ++ переняли идеи обобщения Ada, а затем значительно улучшили их с помощью неявной реализации.
Кевин Клайн
1

То, что вы хотите сделать, на самом деле интересно, трудно доказать, правильно это или нет, пока вы не закончили.

Вот некоторые вещи для размышления:

  • Когда вы когда-нибудь намеренно передадите этот объект верхнего уровня более конкретному объекту?
  • Какой код вы намереваетесь вставить в каждый из ваших классов?

Это единственные две вещи, которые могут извлечь пользу из общего корня. В языке это используется для определения нескольких очень распространенных методов и позволяет вам передавать объекты, которые еще не были определены, но они не должны применяться к вам.

Также из личного опыта:

Я использовал инструментарий, когда-то написанный разработчиком, который имел опыт Smalltalk. Он сделал так, чтобы все его классы «Данные» расширяли один класс, и все методы взяли этот класс - пока все хорошо. Проблема заключается в том, что разные классы данных не всегда были взаимозаменяемыми, поэтому теперь мой редактор и компилятор не могли дать мне ЛЮБОЙ помощи в отношении того, что переходить в данную ситуацию, и мне пришлось обращаться к его документам, которые не всегда помогали. ,

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

Билл К
источник
0

Потому что это путь ООП. Важно иметь тип (объект), который может ссылаться на что угодно. Аргумент типа object может принимать все, что угодно. Также есть наследование, вы можете быть уверены, что ToString (), GetHashCode () и т. Д. Доступны для всего и вся.

Даже Oracle осознал важность наличия такого базового типа и планирует удалить примитивы в Java около 2017 года.

Dante
источник
6
Это не путь ООП и не важно. Вы не приводите никаких реальных аргументов, кроме «Это хорошо».
DeadMG
1
@DeadMG Вы можете прочитать аргументы после первого предложения. Примитивы ведут себя не так, как объекты, что вынуждает программиста писать множество вариантов кода одного и того же назначения для объектов и примитивов.
Данте
2
@DeadMG хорошо, он сказал, что это означает, что вы можете предположить ToString()и GetType()будет на каждом объекте. Вероятно, стоит указать, что вы можете использовать переменную типа objectдля хранения любого объекта .NET.
JohnL
Кроме того, вполне возможно написать пользовательский тип, который может содержать ссылку любого типа. Вам не нужны специальные языковые функции, чтобы достичь этого.
DeadMG
У вас должен быть Объект только на уровнях, которые содержат определенный код, у вас никогда не должно быть одного, чтобы связать ваш граф. «Объект» фактически реализует несколько важных методов и действует как способ передать объект в инструментарий, где тип еще даже не был создан.
Билл К