Ответ 1
Мне любопытно, какие соображения должны иметь такие особенности, как это должно быть.
Во-первых, я пишу блог об этой теме, среди прочих. Смотрите мой старый блог:
http://blogs.msdn.com/b/ericlippert/
и мой новый блог:
для многих статей по различным аспектам дизайна языка.
Во-вторых, процесс разработки С# теперь открыт для просмотра публике, поэтому вы можете сами убедиться, что команда разработчиков языка рассматривает при проверке новых предложений. Подробнее см. на странице roslyn.codeplex.com.
Какие затраты будут связаны с добавлением такого типа ключевого слова в библиотеку?
Это зависит от многих вещей. Конечно, нет дешевых и легких функций. Есть только менее дорогие, менее сложные функции. В целом, это затраты, связанные с проектированием, определением, внедрением, тестированием, документированием и поддержкой этой функции. Есть и более экзотические издержки, такие как альтернативные издержки, связанные с тем, что вы не выполняете лучшую функцию, или стоимость выбора функции, которая плохо взаимодействует с будущими функциями, которые мы могли бы добавить.
В этом случае функция, вероятно, просто сделает "ленивое" ключевое слово синтаксическим сахаром для использования Lazy<T>
. Это довольно простая функция, не требующая много фантастического синтаксического или семантического анализа.
В каких ситуациях это может быть проблематично?
Я могу вспомнить ряд факторов, которые заставили бы меня отбросить эту функцию.
Во-первых, это не обязательно; это просто удобный сахар. Это не добавляет новых возможностей для языка. Преимущества не стоят затрат.
Во-вторых, и что еще более важно, он закрепляет особый вид лени в языке. Существует более одного вида лень, и мы можем ошибаться.
Как бывает более одного вида лености? Ну, подумайте, как это будет реализовано. Свойства уже "ленивы", поскольку их значения не вычисляются до тех пор, пока свойство не будет вызвано, но вы хотите больше этого; вам нужно свойство, которое вызывается один раз, а затем значение кэшируется в следующий раз. Под "ленивым", по сути, вы подразумеваете память. Какие гарантии нам нужно создать? Существует много возможностей:
Возможность # 1: не потокобезопасность вообще. Если вы вызываете свойство для "первого" времени на двух разных потоках, все может произойти. Если вы хотите избежать условий гонки, вам нужно добавить синхронизацию самостоятельно.
Возможность № 2: Threadsafe, так что два вызова свойства в двух разных потоках вызывают функцию инициализации, а затем идут, чтобы узнать, кто заполняет фактическое значение в кеше. Предположительно, функция вернет одно и то же значение в обоих потоках, поэтому дополнительная стоимость здесь будет просто в ненужном дополнительном вызове. Но кеш является потокобезопасным и не блокирует поток. (Так как потолочный кеш может быть написан с кодом с низкой блокировкой или без блокировки.)
Код для обеспечения безопасности потоков зависит от стоимости, даже если это код с низким уровнем блокировки. Это приемлемо? Большинство людей пишут, что такое эффективные однопоточные программы; правильно ли добавить накладные расходы на безопасность потоков для каждого ленивого вызова свойства, нужно ли это или нет?
Возможность № 3: Threadsafe, так что есть сильная гарантия того, что функция инициализации будет вызываться только один раз; в кеше нет гонки. Пользователь может иметь неявное ожидание того, что функция инициализации вызывается только один раз; это может быть очень дорого, и два вызова на два разных потока могут быть неприемлемыми. Реализация такого рода лень требует полной синхронизации, когда возможно, что один поток блокируется бесконечно, пока ленивый метод работает в другом потоке. Это также означает, что могут быть взаимоблокировки, если проблема блокировки с ленивым методом.
Это добавляет еще большую стоимость этой функции - стоимость, которую несут люди, которые не используют ее (потому что они пишут однопоточные программы).
Итак, как мы справляемся с этим? Мы могли бы добавить три функции: "lazy not threadsafe", "ленивые потоки с расами" и "ленивые потоки с блокировкой и, возможно, взаимоблокировки". И теперь функция просто стала намного дороже и сложнее документировать. Это создает огромную проблему обучения пользователей. Каждый раз, когда вы предоставляете разработчику такой выбор, вы предоставляете им возможность писать ужасные ошибки.
В-третьих, функция кажется слабой, как указано. Почему лени следует применять только к свойствам? Похоже, что это можно было бы применить обычно через систему типов:
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>
для выполнения моей работы. Это было бы небольшим удобством.