Чому всі класи в .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 That poo. Java не зробила це так.
Данте

2
@dasblinkenlight: FYI, Java, яка копіює C #, з лямбдами, функціями LINQ тощо ...
користувач541686,

Відповіді:


18

Найактуальнішою причиною Objectє контейнери (до дженериків), які можуть містити що завгодно, замість того, щоб переходити в C-стиль "Напишіть це знову за все, що вам потрібно". Звичайно, імовірно, думка, що все повинно бути успадковано від конкретного класу, а потім зловживати цим фактом, щоб повністю втратити кожну крапку безпеки типу, настільки жахлива, що це повинно було бути гігантським попереджувальним світлом щодо доставки мови без дженериків, а також означає що Objectдосконало для нового коду.


6
Це правильна відповідь. Відсутність загальної підтримки була єдиною причиною. Інші аргументи "загальних методів" не є реальними аргументами, оскільки загальні методи не повинні бути загальними - Приклад: 1) ToString: зловживають у більшості випадків; 2) GetHashCode: залежно від того, що робить програма, більшості класів цей метод не потрібен; 3) GetType: не повинен бути методом екземпляра
Codism

2
Яким чином .NET "повністю втрачає кожну частину безпеки типу", "зловживаючи" думкою про те, що все має успадковуватися від конкретного класу? Чи є який-небудь shitcode, який можна записати навколо, Objectякий не можна було б записати void *в C ++?
Carson63000

3
Хто сказав, що я вважаю, що void*це краще? Це не так. Якщо щось. це ще гірше .
DeadMG

void*гірше в C з усіма неявними вказівками покажчиків, але C ++ є більш суворим у цьому плані. Принаймні, ви повинні чітко виступити.
Tamás Szelei

2
@fish: термінологічний підсумок: у C немає такого поняття, як "неявна роля". Каст - це явний оператор, який вказує конверсію. Ви маєте на увазі "неявні перетворення покажчиків ".
Кіт Томпсон

11

Я пам’ятаю, колись Ерік Ліпперт казав, що успадкування від System.Objectкласу дало «найкращу цінність для клієнта». [редагувати: так , він сказав це тут ]:

... Їм не потрібен загальний базовий тип. Цей вибір був зроблений не з необхідності. Це було зроблено з бажання забезпечити найкращу цінність для замовника.

Розробляючи систему типу або щось інше з цього питання, іноді ви досягаєте пунктів рішення - вам доведеться вирішити або X, або ні-X ... Переваги, що мають загальний базовий тип, переважають за витрати, а чиста вигода при цьому нарахована більша, ніж чиста вигода від відсутності загального базового типу. Тому ми обираємо спільний базовий тип.

Це досить неясна відповідь. Якщо ви хочете більш конкретної відповіді, спробуйте задати більш конкретне запитання.

Маючи все, що випливає з System.Objectкласу, забезпечує надійність та корисність, яку я багато поважаю. Я знаю, що всі об'єкти матимуть тип ( GetType), що вони відповідатимуть методам доопрацювання CLR ( Finalize), і що я можу використовувати GetHashCodeїх під час роботи з колекціями.


2
Це хибно. Вам не потрібно GetType, ви можете просто розширити, typeofщоб замінити його функціональність. Що ж стосується Finalize, то насправді багато типів взагалі не підлягають доопрацюванню і не володіють значущим методом фіналізації. Крім того, багато типів не є доступними та конвертованими в рядкові, особливо внутрішні типи, які ніколи не призначені для подібної речі і ніколи не видаються для громадського використання. Усі ці типи мають непотрібно забруднені інтерфейси.
DeadMG

5
Ні, це не недоліки - я ніколи не стверджував , що ви будете використовувати GetType, Finalizeабо GetHashCodeдля кожного System.Object. Я просто заявив, що вони були там, якщо ти їх хотів. Що стосується "їх інтерфейсів без зайвих забруднень", я посилаюсь на свою оригінальну цитату elippert: "найкраще значення для клієнта". Очевидно, що команда .NET була готова вирішувати компроміси.
gws2

Якщо це не потрібно, то це забруднення інтерфейсу. "Якщо ви хочете", це ніколи не є вагомою причиною включити метод. Він повинен бути тільки там, оскільки він вам потрібен, і ваші споживачі зателефонують йому . Інше, це не добре. Крім того, ваша цитата Ліпперта - це звернення до помилок влади, оскільки він також не каже нічого, окрім "Це добре".
DeadMG

Я не намагаюся сперечатися з вашими питаннями @DeadMG - питання було спеціально "Чому всі класи в .NET глобально успадковуються від класу Object", і тому я пов'язав попередній пост хтось дуже близький до команди .NET в Майкрософт, який дав легітимний - хоч і невиразний - відповідь, чому команда з розробки .NET обрала такий підхід.
gws2

Занадто розпливчаста, щоб бути відповіддю на це питання.
DeadMG

4

Я думаю, що дизайнери Java та C # додали кореневий об’єкт, оскільки він по суті був для них безкоштовним, як у вільному обіді .

Витрати на додавання кореневого об'єкта дуже рівні витратам на додавання вашої першої віртуальної функції. Оскільки і CLR, і JVM є середовищем, що збирається сміттям, з об'єктними фіналізаторами, вам потрібно мати принаймні одну віртуальну для вашого java.lang.Object.finalizeабо для вашого System.Object.Finalizeметоду. Отже, витрати на додавання вашого кореневого об’єкта вже передоплачені, ви можете отримати всі переваги, не платячи за це. Це найкраще з обох світів: користувачі, які потребують загального кореневого класу, отримували б те, що хочуть, а користувачі, які не могли б піклуватися менше, змогли б програмувати так, ніби його немає.


4

Мені здається, у вас тут три питання: одне, чому загальний корінь було введено у .NET, друге - які переваги та недоліки цього, а третє - чи гарна ідея, щоб елемент рамки мав глобальний корінь.

Чому .NET має спільний корінь?

У найбільш технічному сенсі мати спільний корінь має важливе значення для рефлексії та для попередніх генеричних контейнерів.

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

Які переваги та недоліки мати спільний корінь у мові?

Перевага мати спільний корінь:

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

Недолік наявності спільного кореня:

  • Це може зловживати посиланням на об’єкт деякої цінності, навіть якщо ви могли використовувати для нього більш точний тип.

Чи варто брати спільний корінь у вашому рамковому проекті?

Звичайно, дуже суб'єктивно, але коли я переглядаю вищезазначений перелік, я обов'язково відповім " так" . Зокрема, остання куля в списку профі - гнучкість подальшої зміни поведінки всіх об'єктів шляхом зміни кореня - стає дуже корисною на платформі, в якій зміни в корені швидше будуть прийняті клієнтами, ніж зміни в цілому мову.

Крім того - хоча це ще більш суб'єктивно - я вважаю, що концепція спільного коріння є дуже елегантною та привабливою. Це також полегшує деяке використання інструменту, наприклад, зараз легко попросити інструмент показати всіх нащадків цього спільного кореня та отримати швидкий, хороший огляд типів рамки.

Звичайно, типи повинні бути принаймні трохи пов'язані для цього, і, зокрема, я ніколи не пожертвував правилом "є-а", щоб мати спільний корінь.


Я думаю, що на c # не впливав просто Java, але в якийсь момент - була Java. Microsoft більше не може використовувати Java, тому втратить мільярди інвестицій. Вони почали з надання мови, що вони вже мали нову назву.
Майк Браун

3

Математично трохи елегантніше мати тип типу, який містить верх , і дозволяє вам визначити мову трохи повніше.

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

У цій точці, рішення мати загальний базовий клас залежить від того, що ви робите. Дуже рідко буває, що багато речей мають спільну поведінку, і корисно посилатися на ці речі лише через загальну поведінку.

Але це буває. Якщо це так, то йдіть прямо вперед, абстрагуючи цю спільну поведінку.


2

Підштовхується до кореневого об’єкта, оскільки вони хотіли, щоб усі класи в рамках підтримували певні речі (отримання хеш-коду, перетворення на рядок, перевірка рівності тощо). І C #, і Java вважають корисним розмістити всі ці загальні риси Об'єкта в якомусь кореневому класі.

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


2
Це зовсім не так. Є багато внутрішніх класів, які ніколи не хешуються, ніколи не відображаються, ніколи не перетворюються на рядки. Непотрібне успадкування та зайві методи, безумовно , порушують багато принципів.
DeadMG

2

Кілька причин, загальна функціональність, якими можуть поділитися всі дитячі класи. І це також дало можливість письменникам фреймворків записати багато інших функціональних можливостей у рамки, які могли використовувати. Прикладом може бути кешування ASP.NET та сеанси. У них можна зберігати майже все, тому що вони написали методи додавання для прийняття об'єктів.

Кореневий клас може бути дуже заманливим, але це дуже, дуже легко використовувати. Чи можна було б замість цього мати кореневий інтерфейс? І просто є один-два маленьких методу? Або це додасть набагато більше коду, ніж потрібно для вашої основи?

Я прошу, мені цікаво, яку функціональність потрібно виставити всім можливим класам у рамках, про який ви пишете. Чи справді будуть використані всі об'єкти? Або більшість із них? І як тільки ви створите кореневий клас, як ви завадите людям додавати до нього випадкові функції? Або випадкові змінні, які вони хочуть бути "глобальними"?

Кілька років тому була дуже велика програма, де я створив кореневий клас. Після декількох місяців розвитку він був заповнений кодом, в якому не було жодного бізнесу. Ми перетворювали стару програму ASP, і кореневий клас був заміною старого файлу global.inc, який ми використовували в минулому. Довелося засвоїти це заняття важким шляхом.


2

Усі автономні купи об’єкту успадковуються Object; це має сенс, тому що всі самостійні об'єкти купи повинні мати певні спільні аспекти, наприклад, засіб визначення їх типу. В іншому випадку, якби у сміттєзбірнику було посилання на купі об'єкта невідомого типу, він би не міг дізнатися, які біти всередині блоку пам'яті, пов'язаного з цим об’єктом, слід вважати посиланням на інші об'єкти купи.

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

Одним із наслідків цього є те, що визначення типу значення ефективно визначає два типи - тип місця зберігання та тип об'єкта купи. Перші можуть бути неявно перетворені в останні, а останні можуть бути перетворені в перші через typecast. Обидва типи перетворення працюють шляхом копіювання всіх публічних та приватних полів з одного примірника відповідного типу в інший. Крім того, можна використовувати загальні обмеження для виклику членів інтерфейсу безпосередньо у сховищі типу цінності, не створюючи попередньо його копії.

Все це важливо, тому що посилання на об'єкти типу значень типу поводяться як посилання класів, а не як типи значень. Розглянемо, наприклад, наступний код:

string testEnumerator <T> (T it) де T: IEnumerator <string>
{
  var it2 = це;
  it.MoveNext ();
  it2.MoveNext ();
  повернути його.Струм;
}
тест на публічну недійсність ()
{
  var theList = новий Список <string> ();
  theList.Add ("Фред");
  theList.Add ("Джордж");
  theList.Add ("Персі");
  theList.Add ("Моллі");
  theList.Add ("Ron");

  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але його поведінка буде сильно відрізнятися від типу значень того ж імені.


+1 за те, що не згадують ToString ()
JeffO

1
Це все про деталі реалізації. Не потрібно піддавати їх користувачеві.
DeadMG

@DeadMG: Що ти маєш на увазі? Поведінка, що спостерігається, List<T>.Enumeratorщо зберігається як a List<T>.Enumerator, суттєво відрізняється від поведінки тієї, яка зберігається в IEnumerator<T>, тим, що зберігання значення колишнього типу до місця зберігання будь-якого типу зробить копію стану перелічувача, при збереженні значення останнього типу у сховище, яке також є останнього типу, не буде зроблено копію.
supercat

Так, але Objectне має нічого спільного з цим фактом.
DeadMG

@DeadMG: Моя думка полягає в тому, що тип купи типу System.Int32також є екземпляром System.Object, але місце зберігання типу System.Int32не містить ні System.Objectпримірника, ні посилання на один.
supercat

2

Цей дизайн насправді відноситься до Smalltalk, що я б багато в чому розглядав як спробу переслідування об'єкта за рахунок майже будь-яких та інших проблем. Як такий, на мою думку, він має тенденцію використовувати орієнтацію на об'єкт, навіть коли інші методи, ймовірно, (або навіть напевно) є вищими.

Наявність єдиної ієрархії з Object(або чимось подібним) в корені дозволяє досить просто (на один приклад) створювати свої колекційні класи як колекції Object, тому тривіально для колекції містити будь-який тип об'єктів.

Натомість за цю досить незначну перевагу ви отримуєте цілу низку недоліків. По-перше, з точки зору дизайну, ви закінчуєте кілька справді божевільних ідей. Принаймні, згідно з поглядом Яви на Всесвіт, що спільного між Атеїзмом та Лісом? Щоб вони обоє мали хеш-коди! Мапа - це колекція? За словами Яви, ні, це не так!

У 70-х, коли розроблявся Smalltalk, ця дурниця була прийнята, насамперед, тому, що ніхто не задумував розумної альтернативи. Хоча Smalltalk був доопрацьований у 1980 році, і до 1983 року Ада (яка включає в себе дженерики) була розроблена. Хоча Ада ніколи не досягала такої популярності, яку передбачили, її генеричні засоби були достатніми для підтримки колекцій об'єктів довільних типів - без божевілля, властивого монолітним ієрархіям.

Коли Java (і меншою мірою .NET) були розроблені, монолітну ієрархію класів, мабуть, розглядали як "безпечний" вибір - з проблемами, але здебільшого відомими проблемами. На відміну від загального програмування, майже всі (навіть тоді) усвідомлювали, що принаймні теоретично є набагато кращим підходом до проблеми, але той, який багато комерційно орієнтовані розробники вважають досить погано дослідженим та / або ризикованим (тобто в комерційному світі , Ада в значній мірі була відхилена як невдача).

Дозвольте мені бути кришталево ясним: монолітна ієрархія була помилкою. Причини цієї помилки були принаймні зрозумілими, але все-таки помилками. Це поганий дизайн, і його проблеми із дизайном поширюються майже на весь код, використовуючи його.

Однак для нового дизайну сьогодні немає жодного розумного питання: використання монолітної ієрархії - це явна помилка та погана ідея.


Варто констатувати, що шаблони, як відомо, були приголомшливими та корисними ще до того, як NET був відправлений.
DeadMG

У комерційному світі Ада була невдалою не стільки через багатослів’я, скільки через відсутність стандартизованих інтерфейсів до сервісів операційної системи. Жоден стандартний API для файлової системи, графіки, мережі тощо не зробив розробку "хостів" програми Ada надзвичайно дорогим.
кевін клайн

@kevincline: Напевно, я мав би сказати, що це дещо інакше - до того, що Ада в цілому була відхилена як збій через її збій на ринку. Відмова від використання Ада була (ІМО) цілком розумною, але відмовлятися від неї вчитися набагато менше (в кращому випадку).
Джеррі Труну

@Jerry: Я думаю, що шаблони C ++ прийняли ідеї Ada generics, а потім значно покращили їх із неявною інстанцією.
Кевін Клайн

1

Що ви хочете зробити насправді цікаво, важко довести, чи це неправильно чи правильно, поки ви не зробите це.

Ось про що можна подумати:

  • Коли ви коли-небудь навмисно передавали цей об'єкт верхнього рівня над більш конкретним об'єктом?
  • Який код ви маєте намір укласти у кожному своєму класі?

Це єдині дві речі, які можуть бути корисними для спільного кореня. У мові він використовується для визначення декількох дуже поширених методів і дозволяє передавати об'єкти, які ще не були визначені, але вони не повинні стосуватися вас.

Також з особистого досвіду:

Я використовував інструментарій, який колись написав розробник, на фоні якого був Smalltalk. Він зробив це так, що всі його класи "Дані" поширили один клас, і всі методи прийняли цей клас - поки що добре. Проблема полягає в тому, що різні класи даних не завжди були взаємозамінними, тому тепер мій редактор і компілятор не могли мені надати жодної допомоги щодо того, що перейти в дану ситуацію, і мені довелося звернутися до його документів, які не завжди допомагали. .

Це була найскладніша у використанні бібліотека, з якою мені доводилося стикатися.


0

Тому що це шлях ООП. Важливо мати тип (об’єкт), який може посилатися на що завгодно. Аргумент об'єкта типу може приймати що завгодно. Існує також спадкування, ви можете бути впевнені, що ToString (), GetHashCode () тощо доступні на будь-що і все.

Навіть Oracle зрозумів важливість існування такого базового типу і планує вилучити примітивів на Яві близько 2017 року


6
Це ні шлях ООП, ні важливий. Ви не даєте фактичних аргументів, окрім "Це добре".
DeadMG

1
@DeadMG Ви можете прочитати аргументи минулого першого речення. Примітиви поводяться інакше, ніж об'єкти, тому змушують програміста написати багато варіантів коду одного призначення для об'єктів та примітивів.
Данте

2
@DeadMG добре, що він сказав, що це означає, що ви можете припускати ToString()і GetType()будете на кожному об'єкті. Напевно, варто зазначити, що ви можете використовувати змінну типу objectдля зберігання будь-якого об’єкта .NET.
JohnL

Крім того, цілком можливо написати визначений користувачем тип, який може містити посилання будь-якого типу. Для цього вам не потрібні спеціальні мовні функції.
DeadMG

Ви повинні мати Об'єкт лише на рівнях, що містять конкретний код, у вас ніколи не має бути просто зв'язати ваш графік. "Об'єкт" реально реалізує кілька важливих методів і виступає як спосіб передати об'єкт в інструментарій, де тип ще не створений.
Білл К
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.