C # Lazy Loaded Otomatik Özellikler


102

C # dilinde,

Otomatik özelliği, belirli bir varsayılan değere sahip geç yüklenen bir otomatik özelliğe dönüştürmenin bir yolu var mı?

Esasen, bunu çevirmeye çalışıyorum ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

farklı bir şeye, varsayılanı belirleyebileceğim ve gerisini otomatik olarak halledecek ...

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

@Gabe: Sınıfın yalnızca bir kez null döndürmemesi durumunda çağrılacağını unutmayın.
RedFilter

Bunu keşfettim ... tekil kalıbı kullanıyor gibi görünüyor
ctorx

Yanıtlar:


113

Hayır yok. Otomatik uygulanan özellikler yalnızca en temel özellikleri uygulamak için çalışır: alıcı ve ayarlayıcı ile destek alanı. Bu tür bir özelleştirmeyi desteklemez.

Ancak Lazy<T>bu kalıbı oluşturmak için 4.0 türünü kullanabilirsiniz.

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

Bu kod _someVariable, Valueifade ilk çağrıldığında değerini tembel olarak hesaplayacaktır . Yalnızca bir kez hesaplanacak ve Valuemülkün gelecekteki kullanımları için değeri önbelleğe alacak


1
Aslında, bana öyle geliyor ki, Lazy tekli kalıbı uyguluyor. Amacım bu değil ... Amacım, tembel olarak örneklenen, ancak içinde yaşadığı sınıfın örneğiyle birlikte yerleştirilen tembel yüklü bir özellik yaratmaktır. Tembel o şekilde davranmıyor gibi görünüyor.
ctorx

19
@ctorx Lazy'in tekil desenle ilgisi yoktur. Tam olarak ne yapmasını istiyorsanız onu yapar.
user247702

8
Not, SomeClass.IOnlyWantToCallYouOnceörneğinizde bir alan başlatıcı ile kullanılmak için statik olmalıdır.
rory.ap

Harika cevap. Birçok tembel özelliğe sahip olmayı bekliyorsanız, kullanabileceğiniz bir Visual Studio parçacığı için cevabımı görün.
Zephryl

40

Muhtemelen alabileceğiniz en özlü, sıfır birleştirme operatörünü kullanmaktır:

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

10
Durumda IOnlyWantToCallYouOncegetiri nullbirden fazla kez onu arayacak.
JaredPar

9
Boş birleştirme operatörü kullanıldığında, yukarıdaki örnek başarısız olacaktır. Doğru sözdizimi şöyledir: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- _SomeVariablenull ise ayarın etrafına parantez eklendiğine dikkat edin .
Metro Smurf

Bu en iyi seçenektir. İlk önce kullandım Lazy<>, ancak amaçlarımız için bu daha iyi çalıştı. En son C # ile daha da kısa yazılabilir. => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Bazılarının ilk bakışta fark edemeyeceği şey, operatörün sağdaki operandı değerlendirip sonucunu döndürmesidir .
RunninglVlan

Tembel iş parçacığı güvenlidir, bu değil
PLopes

15

C # 6'da Expression Bodied Auto-Properties adlı yeni bir özellik vardır ve bu özelliği biraz daha temiz yazmanıza olanak tanır:

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

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

Artık şu şekilde yazılabilir:

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

   public string SomeVariable => _someVariable.Value;
}

Kodun son bölümünde, başlatma aslında tembel değildir. IOnlyWantToCallYouOncesınıfın her örneği oluşturulduğunda yapım sırasında çağrılır.
Tom Blodget

Yani başka bir deyişle bu tembel değil mi?
Zapnologica

@Zapnologica Önceki cevabım biraz yanlıştı ama güncelledim. SomeVariabletembel yüklü.
Alexander Derck

Bu cevap daha çok İfade Gövdeli Otomatik Özellikler için bir adım gibi okur.
Little Endian

@AbleArcher Yeni bir dil özelliğine işaret etmek artık bir satış konuşması mı?
Alexander Derck

5

Öyle değil, özniteliklerin parametrelerinin değer olarak sabit olması gerekir, kodu çağıramazsınız (Statik kod bile).

Bununla birlikte, PostSharp'ın Yönleri ile bir şeyler uygulayabilirsiniz.

Onlara göz atın:

PostSharp


5

İşte benim probleminize bir çözüm uygulamam. Temelde fikir, ilk erişimde bir işlev tarafından ayarlanacak bir özelliktir ve sonraki erişimler, ilkiyle aynı dönüş değerini verecektir.

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

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

Sonra kullanmak için:

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

Elbette işlev işaretçisini etrafta dolaştırmanın ek yükü var, ancak işi benim için yapıyor ve yöntemi tekrar tekrar çalıştırmaya kıyasla çok fazla ek yük fark etmiyorum.


Yapıcıya işlevi vermek daha mantıklı olmaz mıydı? Bu şekilde, onu her seferinde satır içi oluşturmazsınız ve ilk kez kullandıktan sonra imha edebilirsiniz.
Mikkel R. Lund

@ lund.mikkel evet, bu da işe yarar. Her iki yaklaşım için de kullanım durumları olabilir.
deepee1

5
Eğer işlevi kurucuya aktarırsanız, .Net'in Lazy sınıfına çok benzer, o zaman geçirilen işlev statik olmalıdır, bunun çoğu durumda tasarımıma uymadığını biliyorum.
çıtır çıtır

MikkelR.Lund @ Bazen yapıcı değil sadece talep halinde bazı kod yürütmesine (ve bir destek alanı biçiminde sonucunu önbelleğe) istemiyoruz
mamuesstack

4

Operatör ?? = C # 8.0 ve sonraki sürümleri kullanılarak kullanılabilir, bu nedenle şimdi daha da kısa bir şekilde yapabilirsiniz:

private string _someVariable;

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


3

Bu fikrin büyük bir hayranıyım ve proplazy.snippet adını verdiğim aşağıdaki C # snippet'ini sunmak istiyorum. (Bunu içe aktarabilir veya Snippet Manager'dan alabileceğiniz standart klasöre yapıştırabilirsiniz)

İşte çıktısının bir örneği:

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

Snippet dosyasının içeriği: (proplazy.snippet olarak kaydedin)

<?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>

Ben kuracak myPropertyolmak readonlygüvende olmak için,
PLopes

2

Bunun saf C # ile mümkün olduğunu sanmıyorum. Ama böyle bir IL Rewriter kullanarak yapabileceğini PostSharp . Örneğin, özniteliklere bağlı olarak işlevlerden önce ve sonra işleyiciler eklemenize izin verir.


2

Ben böyle yaptım:

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

ve sonra onu beğenebilirsin

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

Bu bağlamda "bunu" nasıl kullanırım?
Riera

@Riera ne demek istiyorsun? Normal mülk gibi. Örneğin public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban

0

https://github.com/bcuff/AutoLazy size böyle bir şey vermek için Fody'yi kullanıyor

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

ve feryat gibi arıyorum

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

1
Bu, yazarların sorusunu yanıtlasa da, bazı açıklayıcı sözcüklerden ve belgelere bağlantılar içermemektedir. Ham kod parçacıkları, etrafında bazı ifadeler olmadan pek yardımcı olmaz. Ayrıca bulabilirsiniz iyi bir cevap yazmayı çok yararlı. Lütfen cevabınızı düzenleyin.
hellow

0

Geç başlatma sırasında bir yapıcı kullanırsanız, aşağıdaki uzantılar da yardımcı olabilir

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

Kullanım

    private Dictionary<string, object> _cache;

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

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

1
Yardımcınızı kullanmanın bir avantajı var mı LazyInitializer.EnsureInitialized()? Söyleyebileceğim kadarıyla, yukarıdaki işlevselliğe ek olarak, LazyInitializerhata işleme ve eşitleme işlevi sağlar. LazyInitializer kaynak kodu .
semaj1919
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.