Propriedades automáticas de carregamento lento C #


100

Em C #,

Existe uma maneira de transformar uma propriedade automática em uma propriedade automática carregada lentamente com um valor padrão especificado?

Basicamente, estou tentando transformar isso ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

em algo diferente, onde posso especificar o padrão e ele trata o resto automaticamente ...

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

@Gabe: Observe que a classe só será chamada uma vez se nunca retornar nulo.
— RedFilter

Eu descobri que ... parece que usa o padrão singleton
— ctorx

Respostas:


112

Não, não há. As propriedades implementadas automaticamente funcionam apenas para implementar as propriedades mais básicas: campo de apoio com getter e setter. Não suporta este tipo de personalização.

No entanto, você pode usar o Lazy<T>tipo 4.0 para criar este padrão

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

Este código calculará preguiçosamente o valor da _someVariableprimeira vez que a Valueexpressão é chamada. Será calculado apenas uma vez e armazenará o valor em cache para usos futuros da Valuepropriedade


1
Na verdade, me parece que o Lazy implementa o padrão singleton. Esse não é meu objetivo ... meu objetivo é criar uma propriedade carregada lentamente que é instanciada lentamente, mas disposta junto com a instância da classe em que reside. Lazy não parece estar agindo dessa forma.
— ctorx

19
@ctorx Lazy não tem nada a ver com o padrão singleton. Ele faz exatamente o que você deseja.
— user247702

8
Observe que SomeClass.IOnlyWantToCallYouOnceem seu exemplo deve ser estático para ser usado com um inicializador de campo.
— rory.ap

Resposta incrível. Veja minha resposta para um snippet do Visual Studio que você pode usar se espera ter muitas propriedades lazy.
— Zephryl

40

Provavelmente, o mais conciso que você pode obter é usar o operador de coalescência nula:

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

10
No caso de IOnlyWantToCallYouOncedevoluções null, irá chamá-lo mais de uma vez.
— JaredPar

9
Ao usar o operador de coalescência nula, o exemplo acima falhará. A sintaxe correta é: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- observe a adição do parêntese ao redor da configuração _SomeVariablese for nulo.
— Metro Smurf de

Essa é a melhor opção. Usei primeiro Lazy<>, mas para nossos propósitos funcionou melhor. Com o C # mais recente, ele também pode ser escrito de forma ainda mais concisa. => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());O que alguns podem não notar à primeira vista é que o operador avalia o operando à direita e retorna seu resultado .
— RunninglVlan

15

Há um novo recurso no C # 6 chamado Expression Bodied Auto-Properties , que permite que você escreva um pouco mais limpo:

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

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

Agora pode ser escrito como:

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

   public string SomeVariable => _someVariable.Value;
}

Na última seção do código, a inicialização não é realmente lenta. IOnlyWantToCallYouOnceseria chamado durante a construção sempre que a classe fosse instanciada.
— Tom Blodget

Em outras palavras, isso não é carregado lentamente?
— Zapnologica

@Zapnologica Minha resposta anterior estava um pouco errada, mas eu a atualizei. SomeVariableé carregado lentamente.
— Alexander Derck

Esta resposta se parece mais com uma proposta de Expression Bodied Auto-Properties.
— Little Endian,

@AbleArcher Mostrar um novo recurso de linguagem é um argumento de venda agora?
— Alexander Derck

5

Não assim, os parâmetros dos atributos devem ter valores constantes, você não pode chamar o código (Mesmo código estático).

No entanto, você pode ser capaz de implementar algo com Aspectos do PostSharp.

Confira:

PostSharp


5

Aqui está minha implementação de uma solução para o seu problema. Basicamente, a ideia é uma propriedade que será definida por uma função no primeiro acesso e os acessos subsequentes produzirão o mesmo valor de retorno do primeiro.

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

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

Então, para usar:

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

Obviamente, há a sobrecarga de passar o ponteiro de função, mas ele faz o trabalho para mim e não noto muita sobrecarga em comparação com a execução do método repetidamente.


Não faria mais sentido dar a função ao construtor? Dessa forma, você não o criaria em linha todas as vezes e poderia descartá-lo depois de usá-lo pela primeira vez.
— Mikkel R. Lund

@ lund.mikkel sim, isso também funcionaria. Podem ser casos de uso para ambas as abordagens.
— deepee1

5
Se você passar a função para o construtor, da mesma forma que a classe Lazy do .Net, então a função passada terá que ser estática, eu sei que isso não se ajusta ao meu projeto em muitos casos.
— crocante

@ MikkelR.Lund Às vezes você não deseja executar algum código no construtor, mas apenas sob demanda (e armazenar em cache o resultado na forma de um campo de apoio)
— mamuesstack

3

Sou um grande fã dessa ideia e gostaria de oferecer o seguinte snippet C #, que chamei de proplazy.snippet. (Você pode importá-lo ou colá-lo na pasta padrão que pode obter no Gerenciador de trechos)

Aqui está um exemplo de sua saída:

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

Aqui está o conteúdo do arquivo de snippet: (salvar como 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

Não acho que isso seja possível com C # puro. Mas você pode fazer isso usando um reescritor IL como o PostSharp . Por exemplo, permite adicionar manipuladores antes e depois das funções, dependendo dos atributos.


1

Eu fiz assim:

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

e depois você pode usá-lo como

       public virtual bool SomeProperty => this.LazyValue(() =>
    {
        return true; 
    });

Como faço para usar "isso" neste contexto?
— Riera

@Riera o que você quer dizer? Exatamente como uma propriedade normal. Por exemplo public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
— Alexander Zuban

1

Operator ?? = está disponível em C # 8.0 e posterior, então agora você pode fazer de forma ainda mais concisa:

private string _someVariable;

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

0

https://github.com/bcuff/AutoLazy usa Fody para dar a você algo assim

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

e eu chamo como abaixo

result.RaporBilgisi = new ExchangeProgramPersonAllDataModel.RaporImza(() => _reportConfigService.GetReportLayout(documentTypeId));

1
Embora possa responder à pergunta do autor, faltam algumas palavras explicativas e links para a documentação. Trechos de código bruto não são muito úteis sem algumas frases em torno dele. Você também pode descobrir como escrever uma boa resposta muito útil. Edite sua resposta.
— hellow

0

Se você usar um construtor durante a inicialização lenta, as extensões a seguir também podem ser úteis

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

Uso

    private Dictionary<string, object> _cache;

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

                    /* _cache ?? (_cache = new Dictionary<string, object>()); */

Existe uma vantagem em usar o seu ajudante LazyInitializer.EnsureInitialized()? Porque pelo que posso dizer, além da funcionalidade acima, LazyInitializeroferece tratamento de erros, bem como funcionalidade de sincronização. Código-fonte do LazyInitializer .
— semaj1919
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.