Чому в 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.