Почему магические методы были реализованы в 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функциональность к существующим типам по факту.
Йорг Миттаг

2
Обратите внимание, что это в основном прикладная объектная ориентация: пока объект отвечает на соответствующие сообщения, считается, что он имеет правильный тип.
Йорг Миттаг

7
Ваши «ранее» и «сейчас» обратные - магический метод был реализован в качестве базовой функциональности для foreachвозврата назад в начале. Там никогда не было требования для объекта для реализации IEnumerableдля foreachработы. Просто так было принято.
Джесси С. Слайсер

2
@gbulmer, вот откуда я помню, откуда пришла идея: blogs.msdn.com/b/ericlippert/archive/2011/06/30/… (и в меньшей степени ericlippert.com/2013/07/22/… )
Джесси С. Слайсер

Ответы:


16

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

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

  1. Используйте неуниверсальный IEnumerableи IEnumeratorработать сobject s, что означает типы значений бокса. Поскольку итерация чего-то вроде списка 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 to Object для всех типов, которые реализуют IEnumerable<T> ), что означает, что невозможно использовать интерфейс.


Для await, GetResult()метод может возвращать один voidили несколько типов T. Опять же, вы не можете иметь один интерфейс, который может обрабатывать оба. Хотя awaitэто частично основано на интерфейсе: ожидающий должен реализовать, INotifyCompletionа также может реализовать ICriticalNotifyCompletion.


1
Вы уверены, что «они выбрали вариант № 3» для C # 1.0? Насколько я помню, это был вариант 1. Моя память такова, что C # заявлял о значительном превосходстве над Java, потому что компилятор C # выполнял 'auto-boxing' и 'auto-unboxing'. Утверждалось, что 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.