Чому в C # були реалізовані магічні методи?


16

У C # я почав бачити, як усі ці магічні методи спливають, не підкріплюючись інтерфейсом. Чому саме цього обрали?

Дозволь пояснити.

Раніше в C #, якщо об'єкт реалізував IEnumerableінтерфейс, він автоматично був би ітерабельний foreachциклом. Це має сенс для мене, оскільки це підкріплене інтерфейсом, і якби я мав власну Iteratorфункцію всередині класу, що переглядається, я можу це зробити, не переживаючи, що це магічно означатиме щось інше.

Тепер, мабуть, (не впевнений, коли) ці інтерфейси більше не потрібні. Просто потрібно мати правильні перетворення імен.

Інший приклад - зробити будь-який об’єкт очікуваним, маючи метод, названий саме таким, GetAwaiter який має кілька специфічних властивостей.

Чому б не створити інтерфейс, як вони зробили з IEnumerableабо INotifyPropertyChangedпідтримати цю "магію" статично?

Детальніше про те, що я маю на увазі тут:

http://blog.nem.ec/2014/01/01/magic-methods-c-sharp/

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


4
Ви повинні відредагувати свою особисту думку, чому "магія погана", якщо ви не хочете закриватися.
— DougM

2
Якщо ви хочете знати, чому це зробив Андерс Хейльсберг, вам доведеться запитати його. Я можу лише сказати вам, чому я це зробив, і це для сумісності вперед. Методи розширення дозволяють "підробляти" додавання методів до існуючих типів, але інтерфейсів розширень немає. Якщо вам потрібен інтерфейс для, скажімо, async/ await, тоді він працюватиме лише з кодом, написаним після .NET 4.5 став досить широким розповсюдженням, щоб бути життєздатною ціллю ... що в основному є зараз. Але суто синтаксичний переклад на виклики методів дозволяє мені додавати awaitфункціональність до існуючих типів після факту.
— Йорг W Міттаг

2
Зауважте, що це в основному застосована орієнтація на об'єкт: доки об'єкт відповідає на відповідні повідомлення, він вважається правильним.
— Йорг W Міттаг

7
Ваші "раніше" та "зараз" відстали - магічний метод був реалізований як базовий функціонал foreachциклу ще на початку. Там ніколи не було вимогою для об'єкта для реалізації IEnumerableдля foreachдо роботи. Просто це було домовлено про це.
— Jesse C. Slicer

2
@gbulmer - це те, де я пригадую ідею від: blogs.msdn.com/b/ericlippert/archive/2011/06/30/… (і в меншій мірі ericlippert.com/2013/07/22/… )
— Jesse C. Slicer

Відповіді:


16

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

Коли foreachвперше був представлений у C # 1.0 (така поведінка, безумовно, не нещодавнє), йому довелося використовувати магічні методи, оскільки не було генериків. Варіанти в основному були:

  1. Використовуйте негенеровану IEnumerableта IEnumeratorпрацюйте з objects, що означає типи значень боксу. Оскільки повторення чогось подібного до списку ints повинно бути дуже швидким і, безумовно, не повинно створювати багато значень, розміщених у смітнику, це не гарний вибір.

  2. Зачекайте генерики. Це, ймовірно, означатиме затримку .Net 1.0 (або принаймні foreach) більш ніж на 3 роки.

  3. Використовуйте магічні методи.

Таким чином, вони обрали варіант №3, і він залишився у нас з міркувань зворотньої сумісності, навіть незважаючи на те, що .Net 2.0, вимагаючи IEnumerable<T>, працював би теж.


Ініціалізатори колекції можуть виглядати по-різному для кожного типу колекції. Порівняйте List<T>:

public void Add(T item)

і Dictionary<TKey, TValue> :

public void Add(TKey key, TValue value)

Ви не можете мати єдиного інтерфейсу, який підтримував би лише першу форму для List<T> та лише другу для Dictionary<TKey, TValue>.


Методи LINQ зазвичай реалізуються як методи розширення (так що може бути лише одна реалізація, наприклад, LINQ для Object для всіх типів, які реалізують IEnumerable<T>), а це означає, що використовувати інтерфейс неможливо.


Для await, The GetResult()метод може повертати або voidабо який - то тип T. Знову ж таки, у вас не може бути єдиного інтерфейсу, який може працювати з обома. Хоча awaitчастково на основі інтерфейсу: офіціант повинен реалізувати, INotifyCompletionа також може реалізувати ICriticalNotifyCompletion.


1
Ви впевнені, що "вибрали варіант №3" для C # 1.0? Мій спогад, це був варіант 1. Моя пам'ять, C # вимагає значної переваги над Java, оскільки компілятор C # робив "автобокс" та "авторозпакування". Як стверджувалося, C # зробив такий код, як цей foreach, працює набагато краще, ніж Java, частково через це.
— gbulmer

1
Документація для foreachC # 1.2 (в MSDN для C # 1.0 немає нічого) говорить про те, що вираз у foreach"Оцінюється до типу, який реалізується, IEnumerableабо типу, який оголошує GetEnumeratorметод". І я не впевнений, що ви маєте на увазі під цим розпакуванням в C # завжди явний.
— svick

1
@gbulmer І специфікація ECMA для C # від грудня 2001 р. (що означає, що це для C # 1.0) також говорить, що (§15.8.4): "Тип C, як кажуть, є типом колекції, якщо він реалізує інтерфейс System.IEnumerable або реалізує модель колекції, виконуючи всі наведені нижче критерії […] ”.
— svick

1
@svick foreach(T x in sequence)застосовує явні касти до Tелементів послідовності. Отже, якщо послідовність є звичайною IEnumerableі Tє типом значення, вона буде розблоковуватися без явного введення тексту у ваш код. Одна з найгучніших частин C #.
— CodesInChaos

@CodesInChaos "Авторозпакування" звучить досить загально, я не розумів, що він посилається на цю специфічну особливість. (Напевно, мав би, оскільки ми говорили про це foreach.)
— svick
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.