2011-09-27 82 views
45

我最近开始使用Lazy在我的应用程序,如果有,我需要使用Lazy<T>时考虑任何明显的消极方面,我想知道?懒惰的缺点<T>?

我试图利用Lazy<T>经常因为我认为合适,主要是为了帮助减少我们装的内存空间,但不活跃的插件。

+4

我刚开始使用懒惰,发现它通常表示设计不好;或程序员的懒惰。另外,一个缺点是你必须对范围变量保持警惕,并创建适当的关闭。 – Gleno

+4

@Gleno为什么这个程序员的懒惰? –

+4

@Gleno,Anton:更重要的是,它为什么不好?我在编程课上总是教导懒惰是程序员的一个重要优点。 –

回答

17

我会扩大我的评论,其内容有点:

我用懒惰刚刚起步,并发现它的常反映不好的设计 ;或程序员的懒惰。此外,一个 缺点是,你必须与作用域起来 变量更加警惕,并建立适当的关闭。

例如,我使用Lazy<T>创建的页面,用户可以在我的(无会话)MVC应用中看到。这是一个引导向导,因此用户可能想要随机前往步骤步骤。当握手进行时,一系列Lazy<Page>对象被打包,如果用户指定为步骤,则评估该确切页面。我觉得它提供了不错的表现,但也有一些方面它,我不喜欢,比如我的很多foreach结构现在这个样子:

foreach(var something in somethings){ 
    var somethingClosure = something; 
    list.Add(new Lazy<Page>(() => new Page(somethingClosure)); 
} 

即你必须非常积极地处理关闭问题。否则,我不认为这是一个糟糕的性能打击存储一个lambda并在需要时评估它。

另一方面,这可能表示程序员是Lazy<Programmer>,因为您现在不想考虑您的程序,而是在需要时让适当的逻辑进行评估,例如在我的例子中情况 - 而不是建立这个数组,我只能弄清楚那个特定的请求页面是什么;但我选择了懒惰,并采取全方位的做法。

编辑

它发生,我认为Lazy<T>并发工作时,也有一些peculiars。例如,对于某些场景,有一个ThreadLocal<T>,以及针对您的特定多线程场景的多个标志配置。你可以阅读更多关于msdn

+3

这不是'懒惰'本身的问题。相反,这是你如何使用它。 –

+3

@安顿,是的;我的猜想是Lazy <>有时会给你提供不寻找更好解决方案的问题。鉴于这种选择,你可能会解决一些刚刚起作用的问题。 – Gleno

+0

@富士,让我们这样说吧 - 可能发生的最糟糕的情况是,你突然需要评估所有懒惰物体,因为规格的变化,或者面临重大改写。你能忍受吗? – Gleno

4

与其他任何东西一样,Lazy<T>可用于善意或邪恶,因此是一个缺点:如果使用不当,可能会导致混淆和沮丧。然而,懒惰的初始化模式已经存在多年了,现在.NET BCL有了一个实现,开发人员不需要再次重新发明轮子。更何况,MEF loves Lazy

2

究竟你的意思“在我的应用程序”什么?

我认为,当你不知道,如果该值将被使用或没有,这可能仅仅是与需要很长的时间来计算可选参数的情况下,它应该只被使用。这可能包括复杂的计算,文件处理,Web服务,数据库访问等等。

在另一方面,为什么要用Lazy这里?在大多数情况下,你可以简单地调用一个方法而不是lazy.Value,反正也没有区别。但是对于程序员来说更简单明了的是在这种情况下没有Lazy

一个明显的上攻可能已经实现了价值的缓存,但我不认为这是一个大的优势。

7

这不是一个负面的方面,但对懒惰的人来说是一个陷阱:)。

延迟初始化程序就像静态初始化程序。他们运行一次。如果引发异常,则缓存该异常,随后对.Value的调用将抛出相同的异常。这是设计并在文档中提到... http://msdn.microsoft.com/en-us/library/dd642329.aspx

缓存由valueFactory引发的异常。

因此,代码如下绝不会返回一个值:

bool firstTime = true; 
Lazy<int> lazyInt = new Lazy<int>(() => 
{ 
    if (firstTime) 
    { 
     firstTime = false; 
     throw new Exception("Always throws exception the very first time."); 
    } 

    return 21; 
}); 

int? val = null; 
while (val == null) 
{ 
    try 
    { 
     val = lazyInt.Value; 
    } 
    catch 
    { 

    } 
} 
+0

谢谢@ Thilak。这很有趣。我不知道异常是像这样缓存的。 – eandersson

+1

@Fuji这就是为什么MEF团队在MEF 2中添加了ExportFactory和ExportLifetimeContext。请看http://blogs.msdn.com/b/bclteam/archive/2011/11/17/exportfactory-amp-lt-t -amp-gt-in-mef-2-alok.aspx –

+0

我想我现在需要回去修改一些MEF代码。;) – eandersson

7

在我看来,你应该总是有选择懒惰的理由。根据使用情况有几种选择,肯定有这种结构适合的情况。但不要仅仅因为它很酷就使用它。

例如我不明白在页面选择示例中的一点在其他的答案之一。使用Lazy列表选择单个元素可以直接使用委托的列表或字典,而无需使用Lazy或简单的switch语句。

所以最明显的替代品

  • 直接实例廉价的数据结构或者是无论如何需要
  • 代表们所需要的零到几十倍的一些算法
  • 一些缓存结构的东西结构对于应该释放内存时不使用一段时间
  • 某种类似任务“未来”的结构已经可以开始实际使用之前异步初始化消耗的情况下wher CPU空闲时间的项目E中的概率是相当高的,该结构将在

与此相反以后需要,懒惰是常合适的当需要零到在多次

  • 计算密集的数据结构
  • 一些算法,其中零的情况下有显著概率
  • 和数据是本地的一些方法或类,并可以进行垃圾回收时不使用任何更多的或数据应保存在内存中的整个程序的运行时
5

我来使用Lazy<T>主要是因为它是在加载资源的并发能力,从数据库中。因此我摆脱了锁定对象和可争论的锁定模式。 在我的情况ConcurrentDictionary + Lazy作为一种价值让我很快乐,感谢@Reed科普塞和他blog post

这看起来像下面这样。而不是调用的:

MyValue value = dictionary.GetOrAdd(
          key, 
          () => new MyValue(key)); 

我们将改用ConcurrentDictionary>和 写:

MyValue value = dictionary.GetOrAdd(
          key, 
          () => new Lazy<MyValue>(
           () => new MyValue(key))) 
          .Value; 

Lazy<T>没有缺点注意到至今。

+0

这很漂亮。不幸的是,我今天过于活跃,跑出了票数,所以我无法对您的答案进行投票。 ; *( – eandersson

2

懒惰是用来保存资源,而不是真的需要。这种模式非常好,但实现可能无用。

资源越大,有用就是这种模式。

使用懒惰类的缺点是使用不透明。事实上,你必须在任何地方维护一个额外的间接(.Value)。 当你只需要一个真实类型的实例时,即使你不需要直接使用它,它也会被强制加载。

懒惰是懒惰的发展获得生产力,但这种收益可以通过高使用率损失。

如果你有一个真正的透明实现(例如使用代理模式),它可以消除不利情况,在许多情况下它可能非常有用。

并发性必须在其他方面考虑,并且默认情况下不会在您的类型中实现。它只能包含在客户端代码或类型助手中。