缓存属性:更简单的方法?
Posted
技术标签:
【中文标题】缓存属性:更简单的方法?【英文标题】:Cached Property: Easier way? 【发布时间】:2011-02-04 11:29:46 【问题描述】:我有一个对象,其属性的计算成本很高,因此它们仅在首次访问时计算,然后被缓存。
private List<Note> notes;
public List<Note> Notes
get
if (this.notes == null)
this.notes = CalcNotes();
return this.notes;
我想知道,有没有更好的方法来做到这一点?是否有可能在 C# 中创建缓存属性或类似的东西?
【问题讨论】:
我在这里唯一要注意的是,虽然该属性可能看起来是只读的,但 List 是一个可变对象,因此获得引用的任何人都可以更改它。他们不能使私有成员为空,但他们可以使 List 为空。如果您可以摆脱它,我会返回一个 IEnumerable 或其他东西。但这不是你的直接问题。 Oded 所说的对我来说似乎没问题。 @Kevin:ReadOnlyCollection<Note>
在这里也是一个不错的选择(可以说更好,因为它提供索引并且整个数组都可用)。
除了原始帖子之外,就“关注点分离”而言,如果能够有一个只正常返回数据的类,然后是一个处理的子类,那就太好了缓存数据。除了手动完成之外,似乎没有一种很好的方法来实现这一点。有人吗?
【参考方案1】:
就语法而言,如果你想花哨的话,可以使用null-coalescing 运算符,但它不一定是可读的。
get
return notes ?? (notes = CalcNotes());
编辑:更新由 Matthew 提供。另外,我认为其他答案对提问者更有帮助!
【讨论】:
return notes ?? (notes = CalcNotes());
会稍微高效一些。
对不起,我搞砸了第二个??
...我将其更正为=
。如果值为空,此版本将仅执行辅助分配。您的版本将始终重新分配该值。
@Matthew 这正是我今天处理任何惰性属性的方式。
看起来真的很酷!肯定会将此语法用于
那么 ??= 运算符呢? notes ??= CalcNotes()
【参考方案2】:
在 .NET 3.5 或更早版本中,您所拥有的是一种非常标准的做法,并且是一个很好的模型。
(不过,如果可能,我建议返回 IList<T>
或 IEnumerable<T>
,而不是公共 API 中的 List<T>
- List<T>
应该是一个实现细节......)
然而,在 .NET 4 中,这里有一个更简单的选项:Lazy<T>
。这让您可以:
private Lazy<IList<Note>> notes;
public IEnumerable<Note> Notes
get
return this.notes.Value;
// In constructor:
this.notes = new Lazy<IList<Note>>(this.CalcNotes);
【讨论】:
看起来很有趣,可惜你还需要一个支持变量。代码量在整个课程中分布得更多,而且编写的代码并没有减少,所以我想说没有真正的好处。应该有一个 Lazy Property 的构造方式,就像 get; private set; 但随后引用了需要调用的计算方法。 Peterdk:这基本上就是Lazy<T>
给你的东西。如果没有单独的方法来定义构造函数,很难通过 func对我来说看起来很标准。你做的很好。
【讨论】:
【参考方案4】:??
的问题是如果CalcNotes()
返回null
那么它就不会被缓存了。如果 0
和 NaN
都被允许作为属性值,则值类型也是如此。
“面向方面”的解决方案会更好,例如 Post-Sharp 使用属性然后修改 MSIL(字节码)。
代码如下:
[Cached]
public List<Note> Notes get return CalcNotes();
编辑: CciSharp.LazyProperty.dll 就是这样做的!!!
【讨论】:
不要修补编译器发出的真实代码。期间。【参考方案5】:如果计算的值不是微不足道的,我通常更喜欢使用方法 (GetNotes()
)。没有什么可以阻止您使用方法缓存值,此外,如果适用,您可以添加 [Pure]
属性 (.NET 4) 以指示该方法不会改变对象的状态。
如果你决定坚持以下,我建议:
当你有一个惰性求值的属性时,你应该添加以下属性以确保在调试器中运行的行为与在它之外运行的行为相同:
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
此外,从 .NET 4 开始,您可以使用以下内容:
// 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;
【讨论】:
除非 CalcNotes 是静态的,否则不会编译,因为内联构造需要静态上下文 - 您需要将构造移动到构造函数中... @Reed Copsey:看到评论...? 啊,好吧 - 错过了 ;) 将其定义为内联会误导,IMO。【参考方案6】:是的这是可能的。问题是,这样做你赢了多少——你仍然需要在某个地方初始化代码,所以最多你会保存条件表达式。
不久前,我实现了一个类来处理这个问题。您可以在this question 中找到发布的代码,我会在其中询问这是否是个好主意。答案中有一些有趣的观点,请务必在决定使用之前阅读它们。
编辑:
一个Lazy<T> class 与我上面链接的实现基本相同,已添加到.NET 4 Framework;因此,如果您使用的是 .NET 4,则可以使用它。请参见此处的示例:http://weblogs.asp.net/gunnarpeipman/archive/2009/05/19/net-framework-4-0-using-system-lazy-lt-t-gt.aspx
【讨论】:
【参考方案7】:使用 C# 8 和 null-coalescing assignment 的两行选项:
private List<Note>? notes;
public List<Note> Notes => notes ??= CalcNotes();
【讨论】:
以上是关于缓存属性:更简单的方法?的主要内容,如果未能解决你的问题,请参考以下文章