懒惰的缺点<t>?</t>

时间:2011-09-27 10:02:02

标签: c# mef lazy-evaluation

我最近在整个应用程序中开始使用Lazy,我想知道在使用Lazy<T>时是否有任何明显的消极方面需要考虑?

我正在尝试尽可能多地使用Lazy<T>,主要是为了帮助减少已加载但非活动插件的内存占用。

7 个答案:

答案 0 :(得分:18)

我会对我的评论进行一些扩展,其内容如下:

  

我刚刚开始使用Lazy,并发现它通常是指示性的   糟糕的设计;或者程序员的懒惰。还有,一个   缺点是你需要更加警惕   变量,并创建适当的闭包。

例如,我使用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>在处理并发时也有一些诡计。例如,某些场景有ThreadLocal<T>,特定多线程场景有几个标志配置。您可以在msdn上阅读更多内容。

答案 1 :(得分:8)

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

例如,我在其他一个答案中没有得到页面选择示例中的要点。使用Lazy列表来选择单个元素可以直接使用委托列表或字典来完成,而无需使用Lazy或使用简单的switch语句。

所以最明显的替代方案是

  • 直接实例化无论如何需要的廉价数据结构或结构
  • 代表某些算法需要零到几次的事情
  • 一些缓存结构,用于在不使用一段时间后释放内存的项目
  • 某种“未来”结构,比如Task,在实际使用消耗空闲CPU时间之前已经开始异步初始化,如果概率非常高,以后将需要该结构

与此相反,Lazy通常适用于

  • 计算密集的数据结构
  • 在一些算法中,
  • 需要零到多次,其中零情况具有显着的概率
  • 并且数据是某些方法或类的本地数据,并且可以在不再使用时进行垃圾收集,或者数据应该保存在内存中以用于整个程序的运行时

答案 2 :(得分: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
    {

    }
}

答案 3 :(得分:5)

我开始使用Lazy<T>主要是因为它在从数据库加载资源时具有并发功能。因此,我摆脱了锁定对象和可论证的锁定模式。 在我的情况下ConcurrentDictionary + Lazy作为我的一天的价值,感谢@Reed Copsey和他的blog post

  

如下所示。而不是打电话:

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

我们会使用ConcurrentDictionary&gt;,和   写:

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

到目前为止,Lazy<T>没有发现任何缺点。

答案 4 :(得分:4)

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

答案 5 :(得分:2)

“贯穿整个应用程序”

是什么意思?

我认为只有在您不确定是否使用该值时才应该使用它,这可能只是需要很长时间才能计算的可选参数的情况。这可能包括复杂的计算,文件处理,Web服务,数据库访问等。

另一方面,为什么在这里使用Lazy?在大多数情况下,您可以简单地调用方法而不是lazy.Value,但无论如何它都没有区别。但是对于程序员来说,在没有Lazy的情况下发生的事情更简单明了。

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

答案 6 :(得分:2)

Lazy用于保存资源,而不是真正需要的资源。这种模式非常好,但实现可能毫无用处。

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

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

懒惰是为了提高生产力,但这种增益可能因高使用率而失去。

如果你有一个真正透明的实现(使用代理模式作为例子)它可以摆脱不利,它在很多情况下都非常有用。

并发性必须在另一个方面考虑,而不是默认情况下在您的类型中实现。它必须仅包含在客户端代码中或用于此概念的类型帮助程序。