C # właściwości automatyczne ładowane z opóźnieniem


100

W C #

Czy istnieje sposób na przekształcenie właściwości automatycznej w leniwie ładowaną właściwość automatyczną z określoną wartością domyślną?

Zasadniczo próbuję to zmienić ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

w coś innego, gdzie mogę określić domyślne, a resztę zajmie się automatycznie ...

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

@ Gabe: Zwróć uwagę, że klasa zostanie wywołana tylko raz, jeśli nigdy nie zwróci null.
RedFilter

Odkryłem, że ... wygląda na to, że używa wzorca singleton
ctorx

Odpowiedzi:


112

Nie, nie ma. Właściwości zaimplementowane automatycznie służą tylko do zaimplementowania najbardziej podstawowych właściwości: pola zapasowego z funkcją pobierającą i ustawiającą. Nie obsługuje tego typu dostosowywania.

Możesz jednak użyć Lazy<T>typu 4.0, aby utworzyć ten wzorzec

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

Ten kod leniwie obliczy wartość _someVariableprzy pierwszym Valuewywołaniu wyrażenia. Zostanie obliczona tylko raz i zapisuje wartość do przyszłego wykorzystania Valuenieruchomości


1
Właściwie wydaje mi się, że Lazy implementuje wzorzec singletona. To nie jest mój cel ... moim celem jest utworzenie leniwie ładowanej właściwości, która jest leniwie tworzona, ale usuwana wraz z instancją klasy, w której żyje. Wydaje się, że Lazy nie gra w ten sposób.
ctorx

19
@ctorx Lazy nie ma nic wspólnego ze wzorcem singleton. Robi dokładnie to, czego chcesz.
user247702,

8
Uwaga: SomeClass.IOnlyWantToCallYouOncew Twoim przykładzie musi być statyczny, aby można go było używać z inicjatorem pola.
rory.ap

Świetna odpowiedź. Zobacz moją odpowiedź na fragment kodu programu Visual Studio, którego możesz użyć, jeśli spodziewasz się wielu leniwych właściwości.
Zephryl

40

Prawdopodobnie najbardziej zwięzłym, jaki można uzyskać, jest użycie operatora koalescencji zerowej:

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

10
W przypadku IOnlyWantToCallYouOncezwrotów nullbędzie to wywoływać więcej niż raz.
JaredPar

9
W przypadku korzystania z operatora łączenia wartości null powyższy przykład zakończy się niepowodzeniem. Prawidłowa składnia to: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- zwróć uwagę na dodanie nawiasów wokół ustawienia, _SomeVariablejeśli jest puste.
Metro Smurf

To najlepsza opcja. Najpierw użyłem Lazy<>, ale dla naszych celów to działało lepiej. W najnowszym C # można to również napisać jeszcze bardziej zwięźle. => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Niektórzy mogą nie zauważyć na pierwszy rzut oka, że ​​operator ocenia prawostronny operand i zwraca jego wynik .
RunninglVlan

15

W C # 6 jest nowa funkcja o nazwie Expression Bodied Auto-Properties , która pozwala napisać ją nieco bardziej czytelnie:

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

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

Można teraz zapisać jako:

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

   public string SomeVariable => _someVariable.Value;
}

W ostatniej sekcji kodu inicjalizacja nie jest tak naprawdę leniwa. IOnlyWantToCallYouOncebyłaby wywoływana podczas konstruowania za każdym razem, gdy tworzona jest instancja klasy.
Tom Blodget,

Więc innymi słowy to nie jest leniwe ładowanie?
Zapnologica

@Zapnologica Moja poprzednia odpowiedź była trochę błędna, ale zaktualizowałem ją. SomeVariablejest leniwy.
Alexander Derck,

Ta odpowiedź brzmi bardziej jak tonacja na temat właściwości automatycznych wyrażenia.
Little Endian

@AbleArcher Wskazanie nowej funkcji językowej jest teraz tematem?
Alexander Derck,

5

Nie tak, parametry atrybutów muszą mieć stałą wartość, nie można wywołać kodu (nawet kodu statycznego).

Możesz jednak być w stanie zaimplementować coś z aspektami PostSharp.

Sprawdź je:

PostSharp


5

Oto moja implementacja rozwiązania Twojego problemu. Zasadniczo idea jest właściwością, która zostanie ustawiona przez funkcję przy pierwszym dostępie, a kolejne dostępy przyniosą tę samą wartość zwracaną co pierwszy.

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

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

Następnie do użycia:

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

Jest oczywiście narzut związany z przekazywaniem wskaźnika funkcji dookoła, ale robi to za mnie i nie zauważam zbyt dużego narzutu w porównaniu z powtarzaniem metody.


Czy nie miałoby większego sensu przekazanie funkcji konstruktorowi? W ten sposób nie tworzysz go w tekście za każdym razem i możesz go pozbyć się po pierwszym użyciu.
Mikkel R. Lund

@ lund.mikkel tak, to też by działało. Mogą to być przypadki użycia dla obu podejść.
deepee1

5
Jeśli przekażesz funkcję konstruktorowi, podobnie jak klasa Lazy .Net, to przekazana funkcja będzie musiała być statyczna, wiem, że w wielu przypadkach nie pasuje to do mojego projektu.
chrupiące

@ MikkelR.Lund Czasami nie chcesz wykonywać kodu w konstruktorze, ale tylko na żądanie (i buforujesz wynik w postaci pola zapasowego)
mamuesstack

3

Jestem wielkim fanem tego pomysłu i chciałbym zaoferować następujący fragment kodu C #, który nazwałem proplazy.snippet. (Możesz go zaimportować lub wkleić do standardowego folderu, który możesz pobrać z Menedżera fragmentów)

Oto próbka jego wyników:

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

Oto zawartość pliku fragmentu: (zapisz jako 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

Nie sądzę, że jest to możliwe w czystym języku C #. Ale możesz to zrobić za pomocą narzędzia do ponownego zapisywania IL, takiego jak PostSharp . Na przykład pozwala na dodawanie programów obsługi przed i po funkcjach w zależności od atrybutów.


2

Operator ?? = jest dostępny w języku C # 8.0 i nowszych, więc możesz teraz zrobić to jeszcze bardziej zwięźle:

private string _someVariable;

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

1

Zrobiłem to tak:

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

a później możesz go używać jak

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

Jak używać „tego” w tym kontekście?
Riera

@Riera co masz na myśli? Tak jak zwykła nieruchomość. Np. public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban

0

https://github.com/bcuff/AutoLazy używa Fody, aby dać ci coś takiego

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

i wołam jak poniżej

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

1
Chociaż może to odpowiedzieć na pytanie autorów, brakuje w nim słów wyjaśniających i linków do dokumentacji. Fragmenty surowego kodu nie są zbyt pomocne bez otaczających je fraz. Może się również okazać, że bardzo pomocne może być napisanie dobrej odpowiedzi . Zmień swoją odpowiedź.
cześć

0

Jeśli używasz konstruktora podczas leniwej inicjalizacji, poniższe rozszerzenia mogą być również pomocne

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

Stosowanie

    private Dictionary<string, object> _cache;

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

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

1
Czy korzystanie z pomocnika ma jakąś przewagę LazyInitializer.EnsureInitialized()? Ponieważ z tego, co wiem, oprócz funkcji powyżej LazyInitializerzapewnia obsługę błędów, a także funkcjonalność synchronizacji. Kod źródłowy LazyInitializer .
semaj1919
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.