Chr*_*eis 4 .net c# lazy-loading c#-4.0
我有一个cart模型类,它具有如下所示的List属性:
public List<CartItem> CartItems
{
get
{
if (_cartItems == null)
_cartItems = Services.CartItemService.GetCartItems();
return _cartItems;
}
}
private List<CartItem> _cartItems;
Run Code Online (Sandbox Code Playgroud)
这很好,除非用于从SQL Server查询数据的服务返回null,在这种情况下,当引用CartItems时,数据库可能会被多次不必要地命中.然后我注意到Lazy<T>我可以使用,所以我尝试稍微修改我的代码(因为Lazy<T>帐户为null并且会阻止对数据库的多次点击)
public List<CartItem> CartItems
{
get
{
return _cartItems.Value;
}
}
private Lazy<List<CartItem>> _cartItems = new Lazy<List<CartItem>>(() =>
{
// return Services.CartItemService.GetCartItems(); cannot be called here :(
});
Run Code Online (Sandbox Code Playgroud)
编译时错误是
"字段初始值设定项不能引用非静态字段,方法或属性"
服务是与CartItems在同一类中的公共属性,但我无法弄清楚是否可以在Func<List<CartItem>>委托中访问它.我不想为每个属性创建工厂类 - 我在很多地方都有这样的东西,我想......好......懒惰.
Eri*_*ert 22
在评论中回答您的问题:
我现在很好奇为什么它在构造函数中工作而不是在我的例子中.
C#对象的构造顺序如下.首先,按照从大多数派生类到最少派生类的顺序执行所有字段初始值设定项.因此,如果您有一个类B和一个派生类D,并且您创建了一个新的D,那么D的字段初始值设定项都会在B的任何字段初始值设定项之前运行.
一旦所有字段初始值设定项都已运行,则构造函数按从最少派生到最多派生的顺序运行.也就是说,首先运行B的构造函数体,然后运行D的构造函数体.
这可能看起来很奇怪,但推理很简单:
首先,显然基类ctor体必须在派生类ctor体之前运行.派生的ctors可能依赖于由基类ctor初始化的状态,但相反的情况不太可能是真的; 基类通常不知道派生类.
其次,显然预期初始化字段在构造函数体运行之前具有其值.当一个字段初始化器就在那里时,构造函数观察一个未初始化的字段会很奇怪.
因此,我们编码生成ctors的方式是每个ctor遵循以下模式:"初始化我的字段,然后调用我的基类ctor,然后执行我的ctor".由于每个人都遵循该模式,所有字段按照从派生到基础的顺序进行初始化,并且所有的ctor体都从base运行到派生.
好了,现在我们已经确定了,当字段初始化程序显式或隐式地引用"this"时会发生什么?为什么要这么做?"this"可能会用于访问一个对象的方法,字段,属性或事件,该对象的字段初始化程序尚未全部运行,并且没有任何构造函数体已运行!显然这是非常危险的.仍然缺少正确操作该类所需的大部分状态.
因此,我们不允许在任何字段初始值设定项中引用任何"this".
当您到达特定的构造函数体时,您知道所有字段初始值设定项都已运行,并且所有基类的所有构造函数都已运行.您有更多证据表明该对象可能处于良好状态,因此在构造函数体中允许"this"访问.(你仍然可以做愚蠢的危险事情;例如,你可以在基类构造函数中调用在派生类中重写的虚方法;派生的构造函数还没有运行,派生的方法可能因此而失败.小心! )
现在你可能会合理地说,两者之间存在很大差异:
class D : B
{
int x = this.Whatever(); // call a method on the base class, whose ctor has not run!
Run Code Online (Sandbox Code Playgroud)
和
class D : B
{
Func<int> f = this.Whatever;
Run Code Online (Sandbox Code Playgroud)
或类似的:
class D : B
{
Func<int> f = ()=>this.Whatever();
Run Code Online (Sandbox Code Playgroud)
这不会叫任何东西.它不会读取任何可能未初始化的状态.显然这是非常安全的.我们可以制定一条规则,说明"当访问是在方法组到委托转换或lambda时,允许在字段初始化程序中进行此访问",对吧?
不.该规则允许围绕安全系统进行最终运行:
class D : B
{
int x = ((Func<int>)this.Whatever)();
Run Code Online (Sandbox Code Playgroud)
我们又回到了同一条船上.因此,我们可以制定一条规则,"当访问在方法组到委托转换中时允许在字段初始化程序中进行此访问,或者lambda和流分析器可以证明在构造函数运行之前未调用委托"嘿,现在语言设计团队和编译器实现,开发,测试和用户教育团队花费了大量的时间,金钱和精力来解决我们一开始不想解决的问题.
对于语言而言,最好使用简单易懂的规则来提升安全性,并且可以正确实现,轻松测试和清晰记录,而不是具有允许模糊场景工作的复杂规则.简单,安全的规则是实例字段初始化程序不能对'this',period有任何显式或隐式引用.
进一步阅读:
您可以在构造函数中创建该字段.将服务调用移动到它自己的方法也可能需要付费.即
private readonly Lazy<List<CartItem>> _cartItems;
public MyClass()
{
_cartItems = new Lazy<List<CartItem>>(GetCartItems);
}
public List<CartItem> GetCartItems()
{
return Services.CartItemService.GetCartItems();
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6419 次 |
| 最近记录: |