Мне любопытно, какие соображения должна пройти такая функция.
Прежде всего, я пишу блог на эту тему, среди прочего.Смотрите мой старый блог:
http://blogs.msdn.com/b/ericlippert/
и мой новый блог:
http://ericlippert.com
, где можно найти множество статей по различным аспектам языкового дизайна.
Во-вторых, процесс разработки C # теперь открыт для всеобщего обозрения, поэтому вы можете сами убедиться в том, что учитывает команда разработчиков языка при проверке предложений новых функций.Подробнее см. https://github.com/dotnet/roslyn/.
Какие затраты будут связаны с добавлением этого вида ключевых слов в библиотеку?
Это зависит от многих вещей.Конечно, нет дешевых и простых функций.Есть только менее дорогие, менее сложные функции.Как правило, это затраты, связанные с разработкой, определением, внедрением, тестированием, документированием и обслуживанием этой функции.Есть и более экзотические затраты, такие как альтернативная стоимость неэффективной функции или стоимость выбора функции, которая плохо взаимодействует с будущими функциями, которые мы могли бы добавить.
В этом случае функция будетвероятно, просто сделайте ключевое слово "lazy" синтаксическим сахаром для использования Lazy<T>
.Это довольно простая функция, не требующая особого синтаксического или семантического анализа.
В каких ситуациях это может быть проблематично?
Я могу вспомнить ряд факторов, которые заставили бы меня отодвинуть эту функцию.
Во-первых, это не обязательно;это просто удобный сахар.Это на самом деле не добавляет новой силы в язык.Выгоды, кажется, не стоят затрат.
Во-вторых, и что еще более важно, это закрепляет особую разновидность лени в языке.Существует более одного вида лени, и мы можем ошибиться.
Как может быть более одного вида лени?Хорошо, подумайте, как это будет реализовано.Свойства уже "ленивы" в том смысле, что их значения не рассчитываются до тех пор, пока не будет вызвано свойство, но вам нужно больше;Вы хотите свойство, которое вызывается один раз, а затем значение кэшируется в следующий раз.Под «ленивым», по сути, вы подразумеваете запомненную собственность.Какие гарантии нам нужно предоставить?Есть много возможностей:
Возможность # 1: Поток вообще не безопасенЕсли вы впервые вызываете свойство в двух разных потоках, может произойти все что угодно.Если вы хотите избежать условий гонки, вы должны добавить синхронизацию самостоятельно.
Возможность # 2: Потокобезопасен, так что два вызова свойства в двух разных потоках вызывают функцию инициализации, а затем пытаются выяснить, кто заполняет фактическое значение в кэше.Предположительно, функция будет возвращать одно и то же значение в обоих потоках, поэтому дополнительные затраты здесь заключаются просто в потраченном дополнительном вызове.Но кэш является поточно-ориентированным и не блокирует поток.(Поскольку потокобезопасный кэш может быть записан с использованием кода с низкой блокировкой или без блокировки.)
Код для обеспечения безопасности потоков стоит дорого, даже если это код с низкой блокировкой.Это приемлемая стоимость?Большинство людей пишут, что фактически являются однопоточными программами;Правильно ли добавлять накладные расходы на безопасность потоков в каждый вызов ленивого свойства, нужно ли это или нет?
Возможность # 3: Потокобезопасен, так что есть надежная гарантия, что функция инициализации будет вызываться только один раз;в кеше гонки нет.Пользователь может иметь неявное ожидание, что функция инициализации вызывается только один раз;это может быть очень дорого, и два вызова в двух разных потоках могут быть неприемлемы.Реализация такого рода лени требует полной синхронизации, когда возможно, что один поток блокируется на неопределенное время, пока ленивый метод выполняется в другом потоке.Это также означает, что могут быть взаимоблокировки, если есть проблема с упорядочиванием блокировок в ленивом методе.
Это добавляет еще больше затрат к этой функции, стоимость, которую в равной степени несут люди, которые не пользуются ею (потому что они пишут однопоточные программы).
Так как мы с этим справимся?Мы могли бы добавить три функции: «lazy not threadsafe», «lazy threadsafe with races» и «lazy threadsafe with blocking» и, возможно, взаимоблокировки ».И теперь эта функция стала намного дороже и труднее документировать.Это создает огромную проблему обучения пользователей .Каждый раз, когда вы предоставляете разработчику такой выбор, вы предоставляете ему возможность писать ужасные ошибки.
В-третьих, заявленная функциональность кажется слабой.Почему лень следует применять только к свойствам?Кажется, что это может быть применено, как правило, через систему типов:
lazy int x = M(); // doesn't call M()
lazy int y = x + x; // doesn't add x + x
int z = y * y; // now M() is called once and cached.
// x + x is computed and cached
// y * y is computed
Мы стараемся не делать небольшие, слабые функции, если есть более общая функция, которая является ее естественным расширением.Но сейчас мы говорим о действительно серьезных затратах на проектирование и реализацию.
Вы найдете это полезным?
Лично?Не очень полезно.Я пишу множество простых ленивых кодов с низким уровнем блокировки, в основном используя Interlocked.Exchange.(Мне все равно, если ленивый метод запускается дважды и один из результатов отбрасывается; мои ленивые методы никогда не бывают такими дорогими.) Шаблон прост, я знаю, что он безопасен, для делегата никогда не выделяются дополнительные объектыили замки, и если у меня есть что-то более сложное, я всегда могу использовать Lazy<T>
, чтобы сделать работу за меня.Было бы небольшое удобство.