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




4 Answers

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

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

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

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

http://ericlippert.com

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

Во-вторых, процесс разработки C # теперь открыт для всеобщего просмотра, поэтому вы можете сами убедиться, что команда разработчиков языка учитывает при проверке новых предложений. Подробнее см. https://github.com/dotnet/roslyn/ .

Какие затраты будут связаны с добавлением такого ключевого слова в библиотеку?

Это зависит от многих вещей. Конечно, нет дешевых и легких функций. Есть только менее дорогие, менее сложные функции. В целом, это затраты, связанные с проектированием, определением, внедрением, тестированием, документированием и поддержкой этой функции. Есть также и более экзотические издержки, такие как альтернативные издержки, связанные с отсутствием лучшей функции, или стоимость выбора функции, которая плохо взаимодействует с будущими функциями, которые мы могли бы добавить.

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

get set

Я хотел бы написать код следующим образом:

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

EDIT: Почему? Я думаю, что это выглядит лучше, чем:

class Zebra
{
    private Lazy<int> _StripeCount;

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

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

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

Мои вопросы:

  1. Какие затраты будут связаны с добавлением такого ключевого слова в библиотеку?
  2. В каких ситуациях это будет проблематично?
  3. Вы найдете это полезным?

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







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

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

class Zebra
{
    private int? stripeCount;

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



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




Related