C # Properti Otomatis Lazy Loaded

100

Di C #,

Apakah ada cara untuk mengubah properti otomatis menjadi properti otomatis yang lambat dimuat dengan nilai default yang ditentukan?

Pada dasarnya, saya mencoba mengubah ini ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

menjadi sesuatu yang berbeda, di mana saya dapat menentukan default dan menangani sisanya secara otomatis ...

[SetUsing(SomeClass.IOnlyWantToCallYouOnce())]
public string SomeVariable {get; private set;}
ctorx.dll
sumber
@ Gabe: Perhatikan bahwa kelas hanya akan dipanggil sekali jika tidak pernah mengembalikan null.
RedFilter
Saya menemukan bahwa ... tampaknya menggunakan pola tunggal
ctorx

Jawaban:

112

Tidak, tidak ada. Properti yang diimplementasikan secara otomatis hanya berfungsi untuk mengimplementasikan properti yang paling dasar: backing field dengan pengambil dan penyetel. Itu tidak mendukung jenis penyesuaian ini.

Namun Anda dapat menggunakan Lazy<T>tipe 4.0 untuk membuat pola ini

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

Kode ini akan dengan malas menghitung nilai _someVariablepertama kali Valueekspresi dipanggil. Ini hanya akan dihitung sekali dan akan menyimpan nilai untuk penggunaan Valueproperti di masa mendatang

JaredPar
sumber
1
Sebenarnya, menurut saya Lazy menerapkan pola tunggal. Itu bukan tujuan saya ... tujuan saya adalah membuat properti yang dimuat malas yang dibuat dengan malas tetapi dibuang bersama dengan instance kelas tempatnya berada. Malas tampaknya tidak melakukan seperti itu.
ctorx
19
@ctorx Lazy tidak ada hubungannya dengan pola tunggal. Ia melakukan persis seperti yang Anda inginkan.
pengguna247702
8
Catatan, SomeClass.IOnlyWantToCallYouOncedalam contoh Anda harus statis untuk digunakan dengan penginisialisasi bidang.
rory.ap
Jawaban yang luar biasa. Lihat jawaban saya untuk cuplikan Visual Studio yang dapat Anda gunakan jika Anda berharap memiliki banyak properti malas.
Zephryl
40

Mungkin yang paling ringkas yang bisa Anda dapatkan adalah menggunakan operator penggabungan-nol:

get { return _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce()); }
Gabe Moothart
sumber
10
Dalam kasus IOnlyWantToCallYouOncepengembalian nullitu akan memanggilnya lebih dari sekali.
JaredPar
9
Saat menggunakan operator penggabungan-nol, contoh di atas akan gagal. Sintaks yang benar adalah: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- perhatikan penambahan tanda kurung di sekitar pengaturan _SomeVariablejika nol.
Metro Smurf
Ini adalah pilihan terbaik. Pertama saya menggunakan Lazy<>, tetapi untuk tujuan kami ini bekerja lebih baik. Dengan C # terbaru itu juga dapat ditulis lebih ringkas => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Apa yang mungkin tidak diperhatikan beberapa orang dari tampilan pertama adalah bahwa operator mengevaluasi operan kanan dan mengembalikan hasilnya .
RunninglVlan
15

Ada fitur baru di C # 6 yang disebut Expression Bodied Auto-Properties , yang memungkinkan Anda menulisnya sedikit lebih bersih:

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

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

Sekarang dapat ditulis sebagai:

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

   public string SomeVariable => _someVariable.Value;
}
Alexander Derck
sumber
Di bagian terakhir kode, inisialisasi sebenarnya tidak malas. IOnlyWantToCallYouOnceakan dipanggil selama konstruksi setiap kali kelas dibuat.
Tom Blodget
Jadi dengan kata lain ini tidak lazy dimuat?
Zapnologica
@Zapnologica Jawaban saya sebelumnya agak salah tetapi saya memperbaruinya. SomeVariablemalas dimuat.
Alexander Derck
Jawaban ini lebih mirip dengan nada untuk Expression Bodied Auto-Properties.
Little Endian
@AbleArcher Menunjukkan fitur bahasa baru adalah hal yang menarik sekarang?
Alexander Derck
5

Tidak seperti itu, parameter untuk atribut harus bernilai konstan, Anda tidak dapat memanggil kode (Bahkan kode statis).

Namun Anda mungkin dapat menerapkan sesuatu dengan PostSharp's Aspects.

Periksa mereka:

PostSharp

Aren
sumber
5

Inilah implementasi saya untuk memecahkan masalah Anda. Pada dasarnya idenya adalah properti yang akan ditetapkan oleh fungsi pada akses pertama dan akses selanjutnya akan menghasilkan nilai pengembalian yang sama seperti yang pertama.

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

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

Kemudian gunakan:

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

Tentu saja ada overhead untuk melewatkan function pointer, tetapi itu melakukan pekerjaan untuk saya dan saya tidak melihat terlalu banyak overhead dibandingkan dengan menjalankan metode berulang kali.

deepee1
sumber
Bukankah lebih masuk akal untuk memberikan fungsi ke konstruktor? Dengan cara ini Anda tidak akan selalu membuatnya sejajar, dan Anda dapat membuangnya setelah Anda menggunakannya pertama kali.
Mikkel R. Lund
@ lund.mikkel ya, itu juga akan berhasil. Mungkin kasus penggunaan untuk kedua pendekatan.
deepee1
5
Jika Anda meneruskan fungsi ke konstruktor, seperti class Lazy .Net, maka fungsi yang diteruskan harus statis, saya tahu ini tidak sesuai dengan desain saya dalam banyak kasus.
renyah
@ MikkelR.Lund Kadang-kadang Anda tidak ingin mengeksekusi beberapa kode di konstruktor tetapi hanya sesuai permintaan (dan
men
3

Saya penggemar berat ide ini, dan ingin menawarkan cuplikan C # berikut yang saya sebut proplazy.snippet. (Anda dapat mengimpor ini atau menempelkannya ke folder standar yang dapat Anda peroleh dari Snippet Manager)

Berikut contoh keluarannya:

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

Berikut konten file cuplikan: (simpan sebagai 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>
Zephryl
sumber
2

Saya tidak berpikir ini mungkin dengan C # murni. Tetapi Anda dapat melakukannya dengan menggunakan penulis ulang IL seperti PostSharp . Misalnya memungkinkan Anda untuk menambahkan penangan fungsi sebelum dan sesudah bergantung pada atribut.

CodesInChaos
sumber
1

Saya melakukannya seperti ini:

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

dan nanti Anda bisa menggunakannya seperti

       public virtual bool SomeProperty => this.LazyValue(() =>
    {
        return true; 
    });
Alexander Zuban
sumber
Bagaimana saya menggunakan "ini" dalam konteks ini?
Riera
@Riera apa maksudmu? Persis seperti properti biasa. Misalnya public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban
1

Operator ?? = tersedia menggunakan C # 8.0 dan yang lebih baru, jadi Anda sekarang dapat melakukannya dengan lebih ringkas:

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();
Carlos Pozos
sumber
0

https://github.com/bcuff/AutoLazy menggunakan Fody untuk memberi Anda sesuatu seperti ini

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

dan saya menelepon seperti di bawah

result.RaporBilgisi = new ExchangeProgramPersonAllDataModel.RaporImza(() => _reportConfigService.GetReportLayout(documentTypeId));
murat_yuceer
sumber
1
Meskipun ini mungkin menjawab pertanyaan penulis, ini kekurangan beberapa kata yang menjelaskan dan tautan ke dokumentasi. Potongan kode mentah tidak terlalu membantu tanpa beberapa frase di sekitarnya. Anda juga mungkin menemukan cara menulis jawaban yang bagus sangat membantu. Harap edit jawaban Anda.
hellow
0

Jika Anda menggunakan konstruktor selama inisialisasi lambat, ekstensi berikut mungkin berguna juga

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

Pemakaian

    private Dictionary<string, object> _cache;

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

                    /* _cache ?? (_cache = new Dictionary<string, object>()); */
Makeman
sumber
Apakah ada keuntungan menggunakan pembantu Anda LazyInitializer.EnsureInitialized()? Karena dari apa yang saya tahu, selain fungsionalitas di atas, LazyInitializermenyediakan fungsi penanganan kesalahan serta sinkronisasi. Kode sumber LazyInitializer .
semaj1919