Thuộc tính tự động được tải C # Lazy


100

Trong C #,

Có cách nào để biến thuộc tính tự động thành thuộc tính tự động được tải chậm với giá trị mặc định được chỉ định không?

Về cơ bản, tôi đang cố gắng biến điều này ...

private string _SomeVariable

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

          return _SomeVariable;
     }
}

thành một cái gì đó khác, nơi tôi có thể chỉ định mặc định và nó tự động xử lý phần còn lại ...

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

@Gabe: Lưu ý rằng lớp sẽ chỉ được gọi một lần nếu nó không bao giờ trả về null.
RedFilter

Tôi phát hiện ra rằng ... nó có vẻ là sử dụng mô hình singleton
ctorx

Câu trả lời:


112

Không có. Các thuộc tính được triển khai tự động chỉ có chức năng triển khai các thuộc tính cơ bản nhất: trường sao lưu với getter và setter. Nó không hỗ trợ loại tùy chỉnh này.

Tuy nhiên, bạn có thể sử dụng Lazy<T>kiểu 4.0 để tạo mẫu này

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

Đoạn mã này sẽ lười biếng tính toán giá trị của _someVariablelần đầu tiên Valuebiểu thức được gọi. Nó sẽ chỉ được tính một lần và sẽ lưu vào bộ nhớ cache giá trị cho các lần sử dụng thuộc Valuetính trong tương lai


1
Trên thực tế, với tôi, nó giống như Lazy thực hiện mô hình singleton. Đó không phải là mục tiêu của tôi ... mục tiêu của tôi là tạo một thuộc tính được tải lười biếng được khởi tạo một cách lười biếng nhưng được xử lý cùng với phiên bản của lớp mà nó sống. Sự lười biếng dường như không hoạt động theo cách đó.
ctorx

19
@ctorx Lazy không liên quan gì đến mô hình singleton. Nó làm chính xác những gì bạn muốn nó làm.
user247702

8
Lưu ý, SomeClass.IOnlyWantToCallYouOncetrong ví dụ của bạn phải tĩnh để được sử dụng với bộ khởi tạo trường.
rory.ap

Câu trả lời tuyệt vời. Hãy xem câu trả lời của tôi cho đoạn mã Visual Studio mà bạn có thể sử dụng nếu bạn muốn có nhiều thuộc tính lười biếng.
Zephryl

40

Có lẽ cách ngắn gọn nhất mà bạn có thể nhận được là sử dụng toán tử null-thanescing:

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

10
Trong trường hợp IOnlyWantToCallYouOncetrả về nullnó sẽ gọi nó nhiều hơn một lần.
JaredPar

9
Khi sử dụng toán tử null-thanescing, ví dụ trên sẽ không thành công. Cú pháp đúng là: _SomeVariable ?? ( _SomeVariable = SomeClass.IOnlyWantToCallYouOnce() );- lưu ý việc bổ sung dấu ngoặc đơn xung quanh thiết lập _SomeVariablenếu nó là rỗng.
Metro Smurf

Đây là lựa chọn tốt nhất. Lần đầu tiên tôi sử dụng Lazy<>, nhưng đối với mục đích của chúng tôi, điều này hoạt động tốt hơn. Với C # mới nhất, nó cũng có thể được viết ngắn gọn hơn nữa => _SomeVariable ?? (_SomeVariable = SomeClass.IOnlyWantToCallYouOnce());Điều mà một số người có thể không nhận thấy ngay từ cái nhìn đầu tiên là toán tử đánh giá toán hạng bên phải và trả về kết quả của nó .
RunninglVlan

15

Có một tính năng mới trong C # 6 được gọi là Expression Bodied Auto-Properties , cho phép bạn viết nó gọn gàng hơn một chút:

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

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

Bây giờ có thể được viết là:

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

   public string SomeVariable => _someVariable.Value;
}

Trong phần mã cuối cùng, việc khởi tạo không thực sự lười biếng. IOnlyWantToCallYouOncesẽ được gọi trong quá trình xây dựng mỗi khi lớp được khởi tạo.
Tom Blodget

Vì vậy, trong các từ khác, đây không phải là lười tải?
Zapnologica

@Zapnologica Câu trả lời trước đây của tôi hơi sai nhưng tôi đã cập nhật nó. SomeVariableđược tải lười biếng.
Alexander Derck,

Câu trả lời này giống như một quảng cáo chiêu hàng cho Thuộc tính tự động của biểu thức.
Little Endian

@AbleArcher Chỉ ra một tính năng ngôn ngữ mới có phải là quảng cáo chiêu hàng không?
Alexander Derck,

5

Không phải như vậy, tham số cho các thuộc tính phải có giá trị không đổi, bạn không thể gọi mã (Ngay cả mã tĩnh).

Tuy nhiên, bạn có thể triển khai một cái gì đó với Các khía cạnh của PostSharp.

Kiểm tra chúng:

PostSharp


5

Đây là cách tôi triển khai giải pháp cho vấn đề của bạn. Về cơ bản, ý tưởng là một thuộc tính sẽ được đặt bởi một hàm ở lần truy cập đầu tiên và các lần truy cập tiếp theo sẽ mang lại giá trị trả về giống như lần đầu tiên.

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

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

Sau đó để sử dụng:

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

Tất nhiên là có chi phí để truyền con trỏ hàm xung quanh, nhưng nó thực hiện công việc đối với tôi và tôi không nhận thấy quá nhiều chi phí so với việc chạy đi chạy lại phương thức.


Sẽ không hợp lý hơn nếu cung cấp hàm cho hàm tạo? Bằng cách này, bạn sẽ không phải tạo nội tuyến mỗi lần và bạn có thể loại bỏ nó sau khi sử dụng lần đầu tiên.
Mikkel R. Lund

@ lund.mikkel vâng, điều đó cũng sẽ hoạt động. Có thể là trường hợp sử dụng cho cả hai cách tiếp cận.
deepee1

5
Nếu bạn truyền hàm cho hàm tạo, giống như lớp Lazy của .Net, thì hàm được truyền vào sẽ phải ở dạng tĩnh, tôi biết điều này không phù hợp với thiết kế của tôi trong nhiều trường hợp.
giòn

@ MikkelR.Lund Đôi khi bạn không muốn thực hiện một số mã trong các nhà xây dựng nhưng chỉ theo yêu cầu (và bộ nhớ cache kết quả trong hình thức của một lĩnh vực sao lưu)
mamuesstack

3

Tôi rất thích ý tưởng này và muốn cung cấp đoạn mã C # sau đây mà tôi gọi là proplazy.snippet. (Bạn có thể nhập đoạn mã này hoặc dán vào thư mục chuẩn mà bạn có thể lấy từ Trình quản lý đoạn mã)

Đây là một mẫu đầu ra của nó:

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

Đây là nội dung tệp đoạn mã: (lưu dưới dạng 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

Tôi không nghĩ rằng điều này là khả thi với C # thuần túy. Nhưng bạn có thể làm điều đó bằng cách sử dụng một trình viết lại IL như PostSharp . Ví dụ, nó cho phép bạn thêm các trình xử lý trước và sau các hàm tùy thuộc vào thuộc tính.


1

Tôi đã làm nó như thế này:

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

và sau này bạn có thể sử dụng nó như

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

Làm cách nào để sử dụng "this" trong ngữ cảnh này?
Riera

@Riera ý bạn là gì? Cũng giống như tài sản thông thường. Ví dụ public ISet<String> RegularProperty {get;set} public string CalculatedProperty => this.LazyValue(() => { return string.Join(",", RegularProperty.ToArray()); });
Alexander Zuban,

0

https://github.com/bcuff/AutoLazy sử dụng Fody để cung cấp cho bạn một cái gì đó như thế này

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

và tôi gọi như dưới đây

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

1
Mặc dù điều này có thể trả lời câu hỏi của các tác giả, nhưng nó thiếu một số từ giải thích và liên kết đến tài liệu. Các đoạn mã thô không hữu ích lắm nếu không có một số cụm từ xung quanh nó. Bạn cũng có thể thấy cách viết một câu trả lời hay rất hữu ích. Vui lòng chỉnh sửa câu trả lời của bạn.
hellow

0

Nếu bạn sử dụng một hàm tạo trong quá trình khởi tạo lười biếng, các phần mở rộng sau cũng có thể hữu ích

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

Sử dụng

    private Dictionary<string, object> _cache;

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

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

Có lợi thế khi sử dụng người trợ giúp của bạn hơn LazyInitializer.EnsureInitialized()không? Bởi vì từ những gì tôi có thể biết, ngoài chức năng trên, LazyInitializercung cấp xử lý lỗi cũng như chức năng đồng bộ. Mã nguồn LazyInitializer .
semaj1919

0

Toán tử ?? = khả dụng bằng C # 8.0 trở lên, vì vậy bây giờ bạn có thể làm điều đó ngắn gọn hơn:

private string _someVariable;

public string SomeVariable => _someVariable ??= SomeClass.IOnlyWantToCallYouOnce();
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.