简体   繁体   中英

Cached Property: Easier way?

I have a object with properties that are expensive to compute, so they are only calculated on first access and then cached.

 private List<Note> notes;
 public List<Note> Notes
    {
        get
        {
            if (this.notes == null)
            {
                this.notes = CalcNotes();
            }
            return this.notes;
        }
    }

I wonder, is there a better way to do this? Is it somehow possible to create a Cached Property or something like that in C#?

As far as syntax goes, you can use the null-coalescing operator if you want to be fancy, but it's not necessarily as readable.

get
{
    return notes ?? (notes = CalcNotes());
}

Edit: Updated courtesy of Matthew. Also, I think the other answers are more helpful to the question asker!

In .NET 3.5 or earlier, what you have is a very standard practice, and a fine model.

(Although, I would suggest returning IList<T> , or IEnumerable<T> if possible, instead of List<T> in your public API - List<T> should be an implementation detail...)

In .NET 4, however, there is a simpler option here: Lazy<T> . This lets you do:

private Lazy<IList<Note>> notes;
public IEnumerable<Note> Notes
{
    get
    {
        return this.notes.Value;
    }
}

// In constructor:
this.notes = new Lazy<IList<Note>>(this.CalcNotes);

Looks pretty standard to me. What you are doing is fine.

The problem with ?? is that if CalcNotes() returns null then it will not be cached any more. Ditto for value types if for example both 0 and NaN are allowed as property value.

Much better would be an "aspect-oriented" solution, something like Post - Sharp does using attributes and then modifying MSIL (bytecode).

The code would look like:

[Cached]
public List<Note> Notes { get { return CalcNotes(); } }

EDIT: CciSharp.LazyProperty.dll does exactly this!!!

Yes it is possible. The question is, how much you are winning by doing it - you still need the initialization code somewhere, so at most you will be saving the conditional expression.

A while ago I implemented a class to handle this. You can find the code posted in this question , where I ask whether it's a good idea. There are some interesting opinions in the answers, be sure to read them all before deciding to use it.

Edit:

A Lazy<T> class that does basically the same as my implementation that I link to above, has been added to the .NET 4 Framework; so you can use that if you are on .NET 4. See an example here: http://weblogs.asp.net/gunnarpeipman/archive/2009/05/19/net-framework-4-0-using-system-lazy-lt-t-gt.aspx

If the value is non-trivial to compute, I generally prefer using a method ( GetNotes() ). There's nothing stopping you from caching the value with a method, plus you can add the [Pure] attribute (.NET 4) if applicable to indicate the method does not alter the state of the object.

If you do decide to stay with the following, I recommend:

Whenever you have a lazily-evaluated property, you should add the following attribute to ensure that running in the debugger behaves the same as running outside of it:

[DebuggerBrowsable(DebuggerBrowsableState.Never)]

Also, starting with .NET 4, you can use the following:

// the actual assignment will go in the constructor.
private readonly Lazy<List<Note>> _notes = new Lazy<List<Note>>(CalcNotes);

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public List<Note> Notes
{
    get { return _notes.Value; }
}

问题是陈旧的,但这是你在2019年的表现 - 默认值的自动属性(C#6.0及更高版本)

public List<Note> Notes { get; private set; } = CalcNotes();

A two-line option with C# 8 and null-coalescing assignment :

private List<Note>? notes;
public List<Note> Notes => notes ??= CalcNotes();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM