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.