Проблемы с добавлением ключевого слова `lazy` в С#

Мне бы хотелось написать такой код:

class Zebra
{
    public lazy int StripeCount
    {
        get { return ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); }
    }
}

РЕДАКТ: Почему? Я думаю, что это выглядит лучше, чем:

class Zebra
{
    private Lazy<int> _StripeCount;

    public Zebra()
    {
        this._StripeCount = new Lazy(() => ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce());
    }

    public lazy int StripeCount
    {
        get { return this._StripeCount.Value; }
    }
}

При первом вызове свойства он будет запускать код в блоке get, а затем просто вернет значение из него.

Мои вопросы:

  • Какие затраты будут связаны с добавлением такого ключевого слова в библиотеку?
  • В каких ситуациях это может быть проблематично?
  • Вы считаете это полезным?

Я не начинаю крестовый поход, чтобы получить это в следующей версии библиотеки, но мне любопытно, какие соображения должны иметь такую ​​функцию, как это должно быть.

Ответ 1

Мне любопытно, какие соображения должны иметь такие особенности, как это должно быть.

Во-первых, я пишу блог об этой теме, среди прочих. Смотрите мой старый блог:

http://blogs.msdn.com/b/ericlippert/

и мой новый блог:

http://ericlippert.com

для многих статей по различным аспектам дизайна языка.

Во-вторых, процесс разработки С# теперь открыт для просмотра публике, поэтому вы можете сами убедиться, что команда разработчиков языка рассматривает при проверке новых предложений. Подробнее см. на странице 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> для выполнения моей работы. Это было бы небольшим удобством.

Ответ 2

В системной библиотеке уже есть класс, который делает то, что вы хотите: System.Lazy<T>

Я уверен, что он может быть интегрирован в язык, но как Эрик Липперт расскажет вам, что добавление функций на язык не является чем-то легким. Необходимо учитывать много вещей, и соотношение выгод/затрат должно быть очень хорошим. Поскольку System.Lazy уже справляется с этим довольно хорошо, я сомневаюсь, что мы это увидим в ближайшее время.

Ответ 4

Это вряд ли будет добавлено к языку С#, потому что вы можете легко сделать это самостоятельно, даже без Lazy<T>.

Простой, но не потокобезопасный пример:

class Zebra
{
    private int? stripeCount;

    public int StripeCount
    {
        get
        {
            if (this.stripeCount == null)
            {
                this.stripeCount = ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce();
            }
            return this.stripeCount;
        }
    }
}

Ответ 5

Вы пытались /Dou вы имеете в виду это?

private Lazy<int> MyExpensiveCountingValue = new Lazy<int>(new Func<int>(()=> ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce()));
        public int StripeCount
        {
            get
            {
                return MyExpensiveCountingValue.Value;
            }
        }

EDIT:

после редактирования вашего сообщения Я бы добавил, что ваша идея определенно более элегантна, но все равно имеет ту же функциональность!!!.

Ответ 6

Если вы не против использования посткомпилятора, CciSharp имеет этой функции:

class Zebra {
  [Lazy] public int StripeCount {
    get { return ExpensiveCountingMethodThatReallyOnlyNeedsToBeRunOnce(); }
  } 
} 

Ответ 7

Посмотрите на тип Lazy<T>. Также спросите Эрика Липперта о добавлении таких вещей на этот язык, он, несомненно, будет иметь представление.