C # Автоматические свойства с отложенной загрузкой

In C#,

Есть ли способ превратить автоматическое свойство в автоматическое свойство с ленивой загрузкой с указанным значением по умолчанию?

По сути, я пытаюсь повернуть это ...

private string _SomeVariable

public string SomeVariable
{
     get
     {
          if(_SomeVariable == null)
          {
             _SomeVariable = SomeClass.IOnlyWantToCallYouOnce();
          }

          return _SomeVariable;
     }
}

во что-то другое, где я могу указать значение по умолчанию, а все остальное он обработает автоматически ...

[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}

person ctorx    schedule 27.10.2010    source источник
comment
@Gabe: обратите внимание, что класс будет вызываться только один раз, если он никогда не вернет null.   -  person D'Arcy Rittich    schedule 27.10.2010
comment
Я обнаружил, что ... похоже, он использует шаблон singleton   -  person ctorx    schedule 28.10.2010


Ответы (12)


Нет, нет. Автореализуемые свойства используются только для реализации самых основных свойств: резервного поля с геттером и сеттером. Он не поддерживает этот тип настройки.

Однако вы можете использовать тип 4.0 Lazy<T> для создания этого шаблона.

private Lazy<string> _someVariable =new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);
public string SomeVariable => _someVariable.Value;

Этот код будет лениво вычислять значение _someVariable при первом вызове выражения Value. Он будет рассчитан только один раз и будет кэшировать значение для будущего использования свойства Value.

person JaredPar    schedule 27.10.2010
comment
На самом деле мне кажется, что Lazy реализует шаблон singleton. Это не моя цель ... моя цель - создать ленивое загруженное свойство, которое лениво создается, но удаляется вместе с экземпляром класса, в котором оно живет. Lazy, похоже, не работает таким образом. - person ctorx; 28.10.2010
comment
@ctorx Lazy не имеет ничего общего с одноэлементным шаблоном. Он делает именно то, что вы хотите. - person user247702; 20.02.2013
comment
Обратите внимание: SomeClass.IOnlyWantToCallYouOnce в вашем примере должен быть статическим, чтобы его можно было использовать с инициализатором поля. - person rory.ap; 01.12.2016
comment
Отличный ответ. См. Мой ответ на фрагмент кода Visual Studio, который можно использовать, если вы ожидаете иметь много ленивых свойств. - person Zephryl; 18.09.2018

Вероятно, наиболее кратким из возможных вариантов является использование оператора объединения с нулем:

get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }
person Gabe Moothart    schedule 27.10.2010
comment
В случае, если IOnlyWantToCallYouOnce вернет null, он вызовет его более одного раза. - person JaredPar; 27.10.2010
comment
При использовании оператора объединения со значением NULL приведенный выше пример завершится ошибкой. Правильный синтаксис: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() ); - обратите внимание на добавление скобок вокруг параметра _SomeVariable, если он равен нулю. - person Metro Smurf; 22.10.2012
comment
Это лучший вариант. Сначала я использовал Lazy<>, но для наших целей это сработало лучше. В последней версии C # это можно сделать еще более кратким => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); Что некоторые могут не заметить с первого взгляда, так это то, что оператор оценивает правый операнд и возвращает его результат. - person RunninglVlan; 04.10.2019
comment
C # 8 позволяет делать public object MyProp => _myProp ??= new object(); - person gregsdennis; 01.07.2021

В C # 6 есть новая функция, которая называется Автоматические свойства с полями выражений < / strong>, что позволяет написать его немного чище:

public class SomeClass
{ 
   private Lazy<string> _someVariable = new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);

   public string SomeVariable 
   {
      get { return _someVariable.Value; }
   }
}

Теперь можно записать как:

public class SomeClass
{
   private Lazy<string> _someVariable = new Lazy<string>(SomeClass.IOnlyWantToCallYouOnce);

   public string SomeVariable => _someVariable.Value;
}
person Alexander Derck    schedule 29.06.2016
comment
В последнем разделе кода инициализация на самом деле не является ленивой. IOnlyWantToCallYouOnce будет вызываться во время построения каждый раз, когда создается экземпляр класса. - person Tom Blodget; 03.07.2016
comment
Другими словами, это не ленивая загрузка? - person Zapnologica; 30.01.2017
comment
@Zapnologica Мой предыдущий ответ был немного неправильным, но я его обновил. SomeVariable загружается лениво. - person Alexander Derck; 30.01.2017
comment
Этот ответ больше похож на подачу авто-свойств Expression Bodied. - person Little Endian; 08.09.2018
comment
@AbleArcher Указание на новую языковую функцию - это уже шаг? - person Alexander Derck; 27.09.2018
comment
Что ж, мне нравится лаконичный ответ ДжаредПара: «Нет, нет». с последующими альтернативами. Прости. - person Little Endian; 27.09.2018

Оператор ?? = является доступно для C # 8.0 и более поздних версий, поэтому теперь вы можете сделать это еще более кратко:

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();

person Carlos    schedule 25.07.2020
comment
На данный момент это лучший ответ для C # 8 и выше. - person Matt Jenkins; 27.02.2021
comment
Хороший. Просто имейте в виду, что он не является потокобезопасным, как оригинальная версия. С некоторыми накладными расходами Lazy<T> при необходимости обеспечивает встроенную безопасность. - person joe; 31.03.2021

Не так, параметры атрибутов должны быть постоянными по значению, вы не можете вызывать код (даже статический код).

Однако вы можете что-то реализовать с помощью аспектов PostSharp.

Проверь их:

PostSharp

person Aren    schedule 27.10.2010

Вот моя реализация решения вашей проблемы. По сути, идея - это свойство, которое будет установлено функцией при первом доступе, а последующие обращения будут давать то же возвращаемое значение, что и первое.

public class LazyProperty<T>
{
    bool _initialized = false;
    T _result;

    public T Value(Func<T> fn)
    {
        if (!_initialized)
        {
            _result = fn();
            _initialized = true;
        }
        return _result;
    }
 }

Затем использовать:

LazyProperty<Color> _eyeColor = new LazyProperty<Color>();
public Color EyeColor
{ 
    get 
    {
        return _eyeColor.Value(() => SomeCPUHungryMethod());
    } 
}

Конечно, передача указателя функции связана с накладными расходами, но она выполняет свою работу за меня, и я не замечаю слишком больших накладных расходов по сравнению с многократным запуском метода.

person deepee1    schedule 26.06.2011
comment
Разве не имеет смысла передать функцию конструктору? Таким образом, вы не будете создавать его каждый раз встраиваемым образом и сможете избавиться от него после первого использования. - person Mikkel R. Lund; 15.01.2014
comment
@ lund.mikkel да, это тоже сработает. Возможны варианты использования обоих подходов. - person deepee1; 15.01.2014
comment
Если вы передадите функцию конструктору, как и класс Lazy .Net, то переданная функция должна быть статической, я знаю, что во многих случаях это не соответствует моему дизайну. - person crunchy; 09.05.2014
comment
@ MikkelR.Lund Иногда вы не хотите выполнять какой-то код в конструкторе, а только по запросу (и кешировать результат в виде вспомогательного поля) - person mamuesstack; 23.03.2017

Я большой поклонник этой идеи и хотел бы предложить следующий фрагмент кода C #, который я назвал proplazy.snippet. (Вы можете либо импортировать его, либо вставить в стандартную папку, которую можно получить из диспетчера фрагментов).

Вот образец его вывода:

private Lazy<int> myProperty = new Lazy<int>(()=>1);
public int MyProperty { get { return myProperty.Value; } }

Вот содержимое файла сниппета: (сохранить как proplazy.snippet)

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>proplazy</Title>
            <Shortcut>proplazy</Shortcut>
            <Description>Code snippet for property and backing field</Description>
            <Author>Microsoft Corporation</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property type</ToolTip>
                    <Default>int</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>The variable backing this property</ToolTip>
                    <Default>myVar</Default>
                </Literal>
                <Literal>
                    <ID>func</ID>
                    <ToolTip>The function providing the lazy value</ToolTip>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>

            </Declarations>
            <Code Language="csharp"><![CDATA[private Lazy<$type$> $field$ = new Lazy<$type$>($func$);
            public $type$ $property$ { get{ return $field$.Value; } }
            $end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>
person Zephryl    schedule 18.09.2018
comment
Я бы установил myProperty равным readonly, на всякий случай - person PLopes; 15.10.2020

Я не думаю, что это возможно с чистым C #. Но вы можете сделать это с помощью перезаписывающего устройства IL, такого как PostSharp. Например, он позволяет добавлять обработчики до и после функций в зависимости от атрибутов.

person CodesInChaos    schedule 27.10.2010

У меня так получилось:

public static class LazyCachableGetter
{
    private static ConditionalWeakTable<object, IDictionary<string, object>> Instances = new ConditionalWeakTable<object, IDictionary<string, object>>();
    public static R LazyValue<T, R>(this T obj, Func<R> factory, [CallerMemberName] string prop = "")
    {
        R result = default(R);
        if (!ReferenceEquals(obj, null))
        {
            if (!Instances.TryGetValue(obj, out var cache))
            {
                cache = new ConcurrentDictionary<string, object>();
                Instances.Add(obj, cache);

            }


            if (!cache.TryGetValue(prop, out var cached))
            {
                cache[prop] = (result = factory());
            }
            else
            {
                result = (R)cached;
            }

        }
        return result;
    }
}

а позже вы можете использовать его как

       public virtual bool SomeProperty => this.LazyValue(() =>
    {
        return true; 
    });
person Alexander Zuban    schedule 24.10.2018
comment
Как мне использовать это в этом контексте? - person Riera; 27.12.2018
comment
@ Риера, что ты имеешь в виду? Как обычная недвижимость. Например. public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); }); - person Alexander Zuban; 05.10.2019

https://github.com/bcuff/AutoLazy использует Fody, чтобы дать вам что-то вроде этого

public class MyClass
{
    // This would work as a method, e.g. GetSettings(), as well.
    [Lazy]
    public static Settings Settings
    {
        get
        {
            using (var fs = File.Open("settings.xml", FileMode.Open))
            {
                var serializer = new XmlSerializer(typeof(Settings));
                return (Settings)serializer.Deserialize(fs);
            }
        }
    }

    [Lazy]
    public static Settings GetSettingsFile(string fileName)
    {
        using (var fs = File.Open(fileName, FileMode.Open))
        {
            var serializer = new XmlSerializer(typeof(Settings));
            return (Settings)serializer.Deserialize(fs);
        }
    }
}
person Sam    schedule 22.06.2017

[Serializable]
public class RaporImza
{
    private readonly Func<ReportConfig> _getReportLayout;
    public RaporImza(Func<ReportConfig> getReportLayout)
    {
        _getReportLayout = getReportLayout;
    }

    private ReportConfig _getReportLayoutResult;
    public ReportConfig GetReportLayoutResult => _getReportLayoutResult ?? (_getReportLayoutResult = _getReportLayout());

    public string ImzaAtanKisiAdi => GetReportLayoutResult.ReportSignatureName;

    public string ImzaAtanKisiUnvani => GetReportLayoutResult.ReportSignatureTitle;
    public byte[] Imza => GetReportLayoutResult.ReportSignature;
}

и я звоню как ниже

result.RaporBilgisi = new ExchangeProgramPersonAllDataModel.RaporImza(() => _reportConfigService.GetReportLayout(documentTypeId));
person murat_yuceer    schedule 12.09.2018
comment
Хотя это может ответить на вопрос авторов, в нем отсутствуют некоторые поясняющие слова и ссылки на документацию. Фрагменты исходного кода не очень полезны без некоторых фраз. Вы также можете найти как написать хороший ответ очень полезным. Пожалуйста, отредактируйте свой ответ. - person hellow; 12.09.2018

Если вы используете конструктор во время ленивой инициализации, следующие расширения также могут быть полезны

public static partial class New
{
    public static T Lazy<T>(ref T o) where T : class, new() => o ?? (o = new T());
    public static T Lazy<T>(ref T o, params object[] args) where T : class, new() =>
            o ?? (o = (T) Activator.CreateInstance(typeof(T), args));
}

использование

    private Dictionary<string, object> _cache;

    public Dictionary<string, object> Cache => New.Lazy(ref _cache);

                    /* _cache ?? (_cache = new Dictionary<string, object>()); */
person Makeman    schedule 28.10.2018
comment
Есть ли преимущество в использовании помощника перед LazyInitializer.EnsureInitialized()? Потому что, насколько я могу судить, помимо вышеперечисленных функций LazyInitializer обеспечивает обработку ошибок, а также функцию синхронизации. Исходный код LazyInitializer. - person semaj1919; 03.01.2019