Как реализовать ленивую загрузку с PostSharp?

Я хотел бы реализовать ленивую загрузку свойств с помощью PostSharp.

Короче говоря, вместо того, чтобы писать

SomeType _field = null;
private SomeType Field
{
    get
    {
        if (_field == null)
        {
            _field = LongOperation();
        }
        return _field;
    }
}

я хотел бы написать

[LazyLoadAspect]
private object Field
{
    get
    {
        return LongOperation();
    }
}

Итак, я определил, что мне нужно создать некоторый код в классе для создания резервного поля, а также внутри метода получения для реализации теста.

С PostSharp я рассматривал возможность переопределения CompileTimeInitialize, но мне не хватает знаний, чтобы разобраться с скомпилированным кодом.

EDIT: вопрос можно распространить на любой метод без параметров, например:

SomeType _lazyLoadedField = null;
SomeType LazyLoadableMethod ()
{
    if(_lazyLoadedField ==null)
    {
        // Long operations code...
        _lazyLoadedField = someType;
    }
    return _lazyLoadedField ;
}

станет

[LazyLoad]
SomeType LazyLoadableMethod ()
{
     // Long operations code...
     return someType;
}

person remio    schedule 02.03.2012    source источник


Ответы (3)


После наших комментариев, кажется, я знаю, чего вы хотите.

[Serializable]
    public class LazyLoadGetter : LocationInterceptionAspect, IInstanceScopedAspect
    {
        private object backing;

        public override void OnGetValue(LocationInterceptionArgs args)
        {
            if (backing == null)
            {
                args.ProceedGetValue();
                backing = args.Value;
            }

            args.Value = backing;
        }

        public object CreateInstance(AdviceArgs adviceArgs)
        {
            return this.MemberwiseClone();
        }

        public void RuntimeInitializeInstance()
        {

        }
    }

Проверочный код

public class test
    {
        [LazyLoadGetter]
        public int MyProperty { get { return LongOperation(); } }
    }
person Dustin Davis    schedule 02.03.2012
comment
Спасибо Дастин. Я уже читал вашу статью раньше, но я был сбит с толку, потому что я понял, что это очень специфично для IoC, и не мог понять, где будет происходить такой вызов, как LongOperation. Посмотрю поближе новыми глазами. - person remio; 02.03.2012
comment
Если вы всегда собираетесь вызывать один и тот же метод, замените материал IoC своим вызовом LongOperation. Проблема возникает, когда вы хотите делать разные вещи, вам нужно создавать разные аспекты или использовать что-то более общее, например, локатор сервисов или фабрику. - person Dustin Davis; 02.03.2012
comment
Хорошо, спасибо, это то, что я подозревал. Действительно, я искал что-то универсальное. Как представлено в моем вопросе, я хочу реализовать свое свойство независимо от аспекта, а затем решить, возможно, позже, лениво загрузить его значение. Точно так же, как .Net4 Lazy, как предложил nemesv, но в стиле АОП. - person remio; 03.03.2012
comment
Извините, я проверил ваше предложение, и это не отвечает моей потребности. Основная проблема заключается в том, что первое, что делает ваш код каждый раз, — это вызывать args.ProceedGetValue();, который вызывает код получателя. И именно то, чего я хочу избежать, — это вызывать геттер более одного раза! Вот почему я говорил о создании резервного поля, чтобы заполнить его вычисленным значением при первом доступе, а затем просто вернуть его. В любом случае, спасибо за ваше время. - person remio; 04.03.2012
comment
Почему вы хотите избежать вызова геттера, какой тогда смысл иметь свойство? - person Dustin Davis; 04.03.2012
comment
Возможно, мой вопрос был недостаточно ясен. Я мог бы быть менее конкретным, чтобы не фокусироваться на свойствах. Наконец, получатель свойства — это просто метод без параметров, и мой вопрос можно было бы расширить до Как я могу реализовать LazyLoading метода, не заботясь о LazyLoad, благодаря PostSharp? Я не хочу избегать вызова геттера, я хочу не вызывать его более одного раза и вместо этого возвращать ранее вычисленный результат при дальнейших вызовах. - person remio; 05.03.2012
comment
@remio Для этого вам не нужны lazyload или postsharp. Вам по-прежнему нужен GETTER, чтобы получить значение резервного поля, но то, что вы хотите сделать, находится в getter, проверьте, находится ли поле в неинициализированном состоянии (возможно, null), если оно есть, затем установите его значение, вызвав LongOperation, затем продолжайте как обычно. При следующем вызове геттера резервное поле теперь инициализируется, поэтому ему не нужно снова запускать LongOperation. Простой. У вас должен быть геттер, чтобы получить значение из свойства. Вы путаете термины, я думаю. - person Dustin Davis; 05.03.2012
comment
У нас явное недопонимание. То, что вы предлагаете мне реализовать, — это именно то, чего я хочу избежать благодаря PostSharp! Я хочу избежать объявления резервного поля и проверки его неинициализированного состояния. Я просто хочу реализовать свой геттер, не думая о резервном поле. Затем, если я чувствую, что LazyLoading интересен для этого геттера, я просто хочу добавить к нему [LazyLoadAspect]. Затем это (будет) автоматически генерировать резервное поле и проверять его инициализированное состояние. - person remio; 08.03.2012
comment
@remio у вас ВСЕГДА есть резервное поле, даже если вы не объявляете его явно, оно создается компилятором в фоновом режиме. То, что вы хотите сделать, это именно то, что я предложил в первый раз с аспектом. Вам ВСЕГДА нужно будет проверять поле поддержки, где хранится значение. - person Dustin Davis; 08.03.2012
comment
Я не понимаю вас в этом вопросе. Если ваше свойство похоже на public int Foo { get { return 5; } }, вспомогательное поле не создается (я только что проверил код IL с помощью рефлектора). И то же самое, если я заменю 5 на LongOperation();. Моя ошибка заключалась в том, что я сосредоточился на геттерах свойств, в то время как моя проблема такая же, как и для простого метода без параметров, как я указал в своем последнем редактировании для моего исходного вопроса. Как я могу реализовать значение LazyLoaded без явного объявления резервного поля и инициализированного состояния? Так или иначе; спасибо за продолжение :) - person remio; 08.03.2012
comment
@remio Думаю, теперь я понял, вам нужно кэширование, а не ленивая загрузка. Смотрите мой обновленный ответ. - person Dustin Davis; 09.03.2012
comment
Спасибо большое, это именно то, что я искал. - person remio; 09.03.2012
comment
У меня еще два вопроса: 1. Вы написали, что я хочу кеширование, а не ленивую загрузку. Что ты имеешь в виду? Для меня LazyLoading — это особый вид кэширования. Каково ваше определение LazyLoading? 2. Можете ли вы дать мне подсказки, которые помогли бы мне понять цель реализации IInstanceScopedAspect (или сказать мне, что я должен преодолеть свою лень и проверить документацию PostSharp). Еще раз спасибо. - person remio; 09.03.2012
comment
@remio Ленивая загрузка — это отсрочка операции (обычно из-за того, что она выполняется долго) до тех пор, пока ресурс не будет фактически запрошен. Цель состоит в том, чтобы подождать, пока что-то действительно понадобится, прежде чем тратить время на его инициализацию. Если ресурс никогда не запрашивается, он не инициализируется. Кэширование — это хранение результата операции. Цель состоит в том, чтобы запустить операцию только один раз (особенно если она выполняется долго). Любые последующие запросы будут возвращать сохраненное значение, передавая фактическую операцию. - person Dustin Davis; 09.03.2012
comment
@remio технически, код, который вы хотите написать, загружается лениво, он не инициализируется, пока не будет запрошен. Но эта операция будет вызываться каждый раз, когда делается запрос, чего вы хотели избежать, поэтому вам нужно кэшировать, чтобы сохранить результат и избежать операции в последующих запросах. Очень тонкая разница. - person Dustin Davis; 09.03.2012
comment
Аспекты @remio по умолчанию имеют статический охват для каждого типа, поэтому один экземпляр аспекта обслуживает все экземпляры вашего класса. Экземпляр 2 перезапишет значение, сохраненное для Экземпляра 1. Аспекты области действия экземпляра равны 1:1 (один экземпляр аспекта для обслуживания одного экземпляра класса). Много хороших материалов здесь programmersunlimited.wordpress.com/postsharp-principals - person Dustin Davis; 09.03.2012

Благодаря ответу и комментариям Дастина Дэвиса я смог работать над своей собственной реализацией, и я просто хотел поделиться ею здесь, чтобы помочь другие люди.

Основные отличия от исходного ответа:

  • Реализовать предложенный вариант «выполнить операцию только один раз» (назначение блокировки).
  • Статус инициализации резервного поля стал более надежным, передав эту ответственность boolean.

Вот код:

[Serializable]
public class LazyLoadAttribute : LocationInterceptionAspect, IInstanceScopedAspect
{
    // Concurrent accesses management
    private readonly object _locker = new object();

    // the backing field where the loaded value is stored the first time.
    private object _backingField;

    // More reliable than checking _backingField for null as the result of the loading could be null.
    private bool _hasBeenLoaded = false;

    public override void OnGetValue(LocationInterceptionArgs args)
    {
        if (_hasBeenLoaded)
        {
            // Job already done
            args.Value = _backingField;
            return;
        }

        lock (_locker)
        {
            // Once the lock passed, we must check if the aspect has been loaded meanwhile or not.
            if (_hasBeenLoaded)
            {
                args.Value = _backingField;
                return;
            }

            // First call to the getter => need to load it.
            args.ProceedGetValue();

            // Indicate that we Loaded it
            _hasBeenLoaded = true;

            // store the result.
            _backingField = args.Value;
        }
    }

    public object CreateInstance(AdviceArgs adviceArgs)
    {
        return MemberwiseClone();
    }

    public void RuntimeInitializeInstance() { }

}
person remio    schedule 16.03.2012

Я думаю, что это требование нельзя точно описать как «ленивую загрузку», но это частный случай более общего аспекта кэширования с хранилищем в AppDomain, но без вытеснения. Общий аспект кэширования мог бы обрабатывать параметры метода.

person Gael Fraiteur    schedule 16.01.2013