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


100

В 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;}

@Gabe: обратите внимание, что класс будет вызываться только один раз, если он никогда не вернет null.
RedFilter

Я обнаружил, что ... похоже, он использует шаблон singleton
ctorx

Ответы:


112

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

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

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

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


1
На самом деле, мне кажется, что Lazy реализует шаблон singleton. Это не моя цель ... моя цель - создать ленивое загруженное свойство, которое лениво создается, но удаляется вместе с экземпляром класса, в котором оно живет. Lazy, похоже, так не работает.
ctorx

19
@ctorx Lazy не имеет ничего общего с одноэлементным шаблоном. Он делает именно то, что вы от него хотите.
user247702

8
Обратите внимание, что SomeClass.IOnlyWantToCallYouOnceв вашем примере он должен быть статическим, чтобы использоваться с инициализатором поля.
rory.ap 01

Отличный ответ. См. Мой ответ на фрагмент кода Visual Studio, который можно использовать, если вы ожидаете иметь много ленивых свойств.
Zephryl

40

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

get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }

10
В случае IOnlyWantToCallYouOnceвозврата nullон вызовет его более одного раза.
JaredPar

9
При использовании оператора объединения со значением NULL приведенный выше пример завершится ошибкой. Правильный синтаксис: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- обратите внимание на добавление круглых скобок вокруг параметра, _SomeVariableесли он равен нулю.
Metro Smurf,

Это лучший вариант. Сначала я использовал Lazy<>, но для наших целей это сработало лучше. В последней => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());версии C # это можно сделать еще более кратким. Что некоторые могут не заметить с первого взгляда, так это то, что оператор вычисляет правый операнд и возвращает свой результат .
RunninglVlan

15

В C # 6 есть новая функция под названием Expression Bodied Auto-Properties , которая позволяет вам писать немного чище:

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;
}

В последнем разделе кода инициализация не является ленивой. IOnlyWantToCallYouOnceбудет вызываться во время построения каждый раз, когда создается экземпляр класса.
Tom Blodget

Другими словами, это не ленивая загрузка?
Zapnologica

@Zapnologica Мой предыдущий ответ был немного неправильным, но я его обновил. SomeVariableлениво загружается.
Александр Дерк

Этот ответ больше похож на презентацию авто-свойств Expression Bodied.
Little Endian

@AbleArcher Указание на новую языковую функцию - это уже шаг?
Александр Дерк

5

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

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

Проверь их:

PostSharp


5

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

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());
    } 
}

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


Разве не имеет смысла передать функцию конструктору? Таким образом, вы не будете создавать его каждый раз встраиваемым образом и сможете избавиться от него после первого использования.
Mikkel R. Lund

@ lund.mikkel да, это тоже сработает. Возможны варианты использования обоих подходов.
deepee1

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

@ MikkelR.Lund Иногда вы не хотите выполнять какой-то код в конструкторе, а только по запросу (и кэшировать результат в виде
вспомогательного

3

Я большой поклонник этой идеи и хотел бы предложить следующий фрагмент кода 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>

2

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


2

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

private string _someVariable;

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

1

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

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; 
    });

Как использовать «это» в этом контексте?
Riera

@ Риера, что ты имеешь в виду? Как обычная недвижимость. Eg public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Александр

0

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);
        }
    }
}

0
[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));

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

0

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

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>()); */

1
Есть ли преимущество в использовании помощника LazyInitializer.EnsureInitialized()? Потому что, LazyInitializerнасколько я могу судить, в дополнение к вышеперечисленным функциям обеспечивает обработку ошибок, а также функцию синхронизации. Исходный код LazyInitializer .
semaj1919 03
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.