C # Lazy Loaded คุณสมบัติอัตโนมัติ


102

ใน C #

มีวิธีเปลี่ยนคุณสมบัติอัตโนมัติให้เป็นคุณสมบัติอัตโนมัติที่โหลดแบบขี้เกียจด้วยค่าเริ่มต้นที่ระบุหรือไม่?

โดยพื้นฐานแล้วฉันพยายามเปลี่ยนสิ่งนี้ ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

เป็นสิ่งที่แตกต่างโดยที่ฉันสามารถระบุค่าเริ่มต้นและจัดการส่วนที่เหลือโดยอัตโนมัติ ...

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

@Gabe: โปรดทราบว่าคลาสจะถูกเรียกเพียงครั้งเดียวหากไม่ส่งกลับค่าว่าง
RedFilter

ฉันค้นพบว่า ... ดูเหมือนว่าจะใช้รูปแบบซิงเกิลตัน
ctorx

คำตอบ:


113

ไม่มีไม่มี คุณสมบัติที่นำไปใช้งานโดยอัตโนมัติทำหน้าที่เพื่อใช้คุณสมบัติพื้นฐานที่สุดเท่านั้น: ฟิลด์สำรองด้วย getter และ setter ไม่รองรับการปรับแต่งประเภทนี้

อย่างไรก็ตามคุณสามารถใช้Lazy<T>ประเภท4.0 เพื่อสร้างรูปแบบนี้ได้

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

รหัสนี้จะคำนวณค่าของ_someVariableครั้งแรกที่Valueเรียกนิพจน์อย่างเกียจคร้าน จะคำนวณเพียงครั้งเดียวและจะแคชค่าสำหรับการใช้งานValueคุณสมบัติในอนาคต


1
ที่จริงแล้วสำหรับฉันดูเหมือนว่า Lazy ใช้รูปแบบซิงเกิลตัน นั่นไม่ใช่เป้าหมายของฉัน ... เป้าหมายของฉันคือสร้างอสังหาริมทรัพย์ที่โหลดอย่างเกียจคร้านที่สร้างขึ้นอย่างเกียจคร้าน แต่ถูกกำจัดไปพร้อมกับตัวอย่างของคลาสที่มันอาศัยอยู่ Lazy ดูเหมือนจะไม่ได้แสดงในลักษณะนั้น
ctorx

19
@ctorx Lazy ไม่มีอะไรเกี่ยวข้องกับรูปแบบซิงเกิลตัน มันทำสิ่งที่คุณต้องการให้ทำ
user247702

8
หมายเหตุSomeClass.IOnlyWantToCallYouOnceในตัวอย่างของคุณต้องเป็นแบบคงที่เพื่อใช้กับตัวเริ่มต้นฟิลด์
rory.ap

คำตอบที่ยอดเยี่ยม ดูคำตอบของฉันสำหรับตัวอย่าง Visual Studio ที่คุณสามารถใช้ได้หากคุณคาดว่าจะมีคุณสมบัติขี้เกียจมากมาย
Zephryl

40

สิ่งที่รัดกุมที่สุดที่คุณจะได้รับคือการใช้ตัวดำเนินการเชื่อมต่อแบบ null:

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

10
ในกรณีIOnlyWantToCallYouOnceส่งคืนnullจะเรียกว่ามากกว่าหนึ่งครั้ง
JaredPar

9
เมื่อใช้ตัวดำเนินการประสานค่า null ตัวอย่างข้างต้นจะล้มเหลว ไวยากรณ์ที่ถูกต้องคือ: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- สังเกตการเพิ่มวงเล็บรอบ ๆ การตั้งค่า_SomeVariableหากเป็นโมฆะ
Metro Smurf

นี่คือตัวเลือกที่ดีที่สุด ก่อนอื่นฉันใช้Lazy<>แต่สำหรับวัตถุประสงค์ของเราสิ่งนี้ได้ผลดีกว่า กับล่าสุด C # ก็ยังสามารถเขียนได้กระชับมากยิ่งขึ้น=> _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());สิ่งที่บางคนอาจจะไม่แจ้งให้ทราบล่วงหน้าจากลักษณะแรกเป็นผู้ประกอบการที่จะประเมินขวามือตัวถูกดำเนินการและผลตอบแทนของ
RunninglVlan

Lazy is thread safe, this is not
PLopes

15

มีคุณสมบัติใหม่ใน C # 6 ที่เรียกว่าExpression Bodied Auto-Propertiesซึ่งช่วยให้คุณเขียนได้สะอาดขึ้นเล็กน้อย:

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

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

ตอนนี้สามารถเขียนเป็น:

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

   public string SomeVariable => _someVariable.Value;
}

ในส่วนสุดท้ายของโค้ดการเริ่มต้นไม่ใช่เรื่องน่าเกียจ IOnlyWantToCallYouOnceจะถูกเรียกในระหว่างการก่อสร้างทุกครั้งที่มีการสร้างอินสแตนซ์ชั้นเรียน
Tom Blodget

ดังนั้นในคำอื่น ๆ นี้ไม่ได้ขี้เกียจโหลด?
Zapnologica

@Zapnologica คำตอบก่อนหน้าของฉันผิดนิดหน่อย แต่ฉันอัปเดตแล้ว SomeVariableขี้เกียจโหลด
Alexander Derck

คำตอบนี้อ่านคล้ายกับสำนวนการขายสำหรับ Expression Bodied Auto-Properties
Little Endian

@AbleArcher ชี้ให้เห็นถึงคุณลักษณะภาษาใหม่ในขณะนี้หรือไม่
Alexander Derck

5

ไม่เช่นนั้นพารามิเตอร์สำหรับแอตทริบิวต์จะต้องมีค่าคงที่คุณไม่สามารถเรียกรหัสได้ (แม้แต่รหัสคงที่)

อย่างไรก็ตามคุณสามารถนำบางสิ่งไปใช้กับแง่มุมของ PostSharp ได้

ตรวจสอบพวกเขา:

PostSharp


5

นี่คือการดำเนินการแก้ไขปัญหาของคุณ โดยพื้นฐานแล้วแนวคิดคือคุณสมบัติที่จะถูกกำหนดโดยฟังก์ชันในการเข้าถึงครั้งแรกและการเข้าถึงในภายหลังจะให้ผลตอบแทนเท่ากับค่าแรก

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

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

จากนั้นจะใช้:

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

แน่นอนว่ามีค่าใช้จ่ายในการส่งตัวชี้ฟังก์ชันไปรอบ ๆ แต่มันก็ใช้ได้ผลสำหรับฉันและฉันไม่สังเกตเห็นค่าใช้จ่ายมากเกินไปเมื่อเทียบกับการเรียกใช้เมธอดซ้ำแล้วซ้ำเล่า


มันจะสมเหตุสมผลกว่าไหมที่จะมอบฟังก์ชันให้กับคอนสตรัคเตอร์ ด้วยวิธีนี้คุณจะไม่ต้องสร้างแบบอินไลน์ในแต่ละครั้งและคุณสามารถกำจัดทิ้งได้หลังจากใช้ครั้งแรก
Mikkel R. Lund

@ lund.mikkel ใช่นั่นก็ใช้ได้เช่นกัน อาจเป็นกรณีการใช้งานสำหรับทั้งสองวิธี
deepee1

5
หากคุณส่งฟังก์ชันไปยังตัวสร้างเหมือนกับคลาส Lazy ของ. Net ฟังก์ชันที่ส่งผ่านจะต้องเป็นแบบคงที่ฉันรู้ว่าสิ่งนี้ไม่เหมาะกับการออกแบบของฉันในหลาย ๆ กรณี
กรุบกรอบ

@ MikkelR.Lund บางครั้งคุณไม่ต้องการรันโค้ดบางตัวในตัวสร้าง แต่ทำตามความต้องการเท่านั้น (และแคชผลลัพธ์ในรูปแบบของฟิลด์สำรอง)
mamuesstack

4

Operator ?? =สามารถใช้งานได้โดยใช้ C # 8.0 และใหม่กว่าดังนั้นคุณจึงสามารถทำได้อย่างรัดกุมยิ่งขึ้น:

private string _someVariable;

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


3

ฉันเป็นแฟนตัวยงของแนวคิดนี้และต้องการเสนอข้อมูลโค้ด C # ต่อไปนี้ซึ่งฉันเรียกว่า proplazy.snippet (คุณสามารถนำเข้าสิ่งนี้หรือวางลงในโฟลเดอร์มาตรฐานซึ่งคุณสามารถรับได้จาก Snippet Manager)

นี่คือตัวอย่างของผลลัพธ์:

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

นี่คือเนื้อหาของไฟล์ตัวอย่าง: (บันทึกเป็น 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>

ฉันจะตั้งค่าmyPropertyให้เป็นreadonlyเพียงเพื่อความปลอดภัย
PLopes

2

ฉันไม่คิดว่าจะเป็นไปได้ด้วย C # ที่บริสุทธิ์ แต่คุณสามารถทำมันใช้ Rewriter IL เช่นPostSharp ตัวอย่างเช่นช่วยให้คุณสามารถเพิ่มตัวจัดการก่อนและหลังฟังก์ชันขึ้นอยู่กับคุณสมบัติ


2

ฉันทำแบบนี้:

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

และหลังจากนั้นคุณสามารถใช้มันเช่น

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

ฉันจะใช้ "this" ในบริบทนี้ได้อย่างไร
Riera

@Riera คุณหมายถึงอะไร? เช่นเดียวกับทรัพย์สินทั่วไป เช่น public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban

0

https://github.com/bcuff/AutoLazyใช้ Fody เพื่อให้สิ่งนี้แก่คุณ

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

และฉันเรียกเหมือนร้อง

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

1
แม้ว่าสิ่งนี้อาจตอบคำถามของผู้เขียน แต่ก็ขาดคำอธิบายและลิงก์ไปยังเอกสารประกอบ ข้อมูลโค้ดดิบจะไม่เป็นประโยชน์มากนักหากไม่มีวลีบางอย่างอยู่รอบ ๆ คุณอาจพบวิธีเขียนคำตอบที่ดีซึ่งมีประโยชน์มาก โปรดแก้ไขคำตอบของคุณ
hellow

0

หากคุณใช้ตัวสร้างในระหว่างการเริ่มต้นที่ขี้เกียจส่วนขยายต่อไปนี้อาจเป็นประโยชน์เช่นกัน

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

การใช้งาน

    private Dictionary<string, object> _cache;

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

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

1
มีข้อได้เปรียบในการใช้ผู้ช่วยของคุณLazyInitializer.EnsureInitialized()หรือไม่? เนื่องจากสิ่งที่ฉันสามารถบอกได้นอกเหนือจากฟังก์ชันข้างต้นLazyInitializerยังมีการจัดการข้อผิดพลาดและฟังก์ชันการซิงค์ รหัสที่มา LazyInitializer
semaj1919
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.