【问题标题】:Disadvantages of Lazy<T>?Lazy<T> 的缺点?
【发布时间】:2011-09-27 10:02:02
【问题描述】:

我最近开始在整个应用程序中使用Lazy,我想知道在使用Lazy&lt;T&gt; 时是否需要考虑任何明显的负面影响?

我正在尝试尽可能频繁地使用Lazy&lt;T&gt;,主要是为了帮助减少我们已加载但不活动的插件的内存占用。

【问题讨论】:

  • 我刚开始使用 Lazy,发现它通常表示设计不佳;或者程序员的懒惰。此外,一个缺点是您必须更加警惕作用域变量,并创建适当的闭包。
  • @Gleno 这个程序员到底为什么懒惰?
  • @Gleno, Anton:更重要的是,为什么它不好?我总是在我的编程课上教导懒惰是程序员的一个重要美德。
  • 我当然也喜欢懒惰,但有时进行懒惰评估可能比考虑使用哪个确切资源更容易。在这种情况下,您将失去理解、简化和美化您自己的代码的机会。

标签: c# mef


【解决方案1】:

我将稍微扩展一下我的评论,内容如下:

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

例如,我使用 Lazy&lt;T&gt; 创建了用户可以在我的 (sessionless) MVC 应用程序中看到的页面。它是一个指导向导,因此用户可能想要随机进入上一步。握手时,Lazy&lt;Page&gt; 对象的数组被创建,如果用户指定为步骤,则评估该确切页面。我发现它提供了很好的性能,但我不喜欢它的某些方面,例如我的许多 foreach 构造现在看起来像这样:

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

即您必须非常主动地处理关闭问题。否则,我认为存储 lambda 并在需要时对其进行评估不会对性能造成如此糟糕的影响。

另一方面,这可能表明程序员是Lazy&lt;Programmer&gt;,从某种意义上说,您现在不想考虑您的程序,而是在需要时让正确的逻辑进行评估,例如我的情况——我可以不构建那个数组,而是弄清楚那个特定的请求页面是什么;但我选择偷懒,全力以赴。

编辑

我突然想到Lazy&lt;T&gt; 在处理并发时也有一些特点。例如,对于某些场景有一个ThreadLocal&lt;T&gt;,对于您的特定多线程场景有几个标志配置。您可以在msdn 上阅读更多内容。

【讨论】:

  • 这不是Lazy&lt;T&gt; 本身的问题。相反,这就是你如何使用它。
  • @Anton,是的;我的猜想是 Lazy 有时 在让您选择不寻找更好的解决方案时存在问题。鉴于该选项,您可能会满足于一些可行的方法。
  • @Fuji,让我们这样说吧 - 可能发生的最糟糕的情况是,由于规范的变化,您突然不得不评估所有 Lazy 对象,或者面临重大的重写。你能忍受吗?
  • +1 for var somethingClosure = something; 我一直在寻找可以称为闭包的东西...哦,我在踢自己。
  • 请注意,在 C# 5.0 (VS2012) 中,默认情况下在闭包中使用当前迭代值......您不再有讨厌的“闭包的额外本地”问题。详情请见msdn.microsoft.com/en-us/library/hh678682(v=vs.110).aspx
【解决方案2】:

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

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

所以最明显的选择是

  • 直接实例化廉价的数据结构或无论如何都需要的结构
  • 代表某些算法中需要零到几次的事情
  • 一些缓存结构,用于在一段时间不使用时释放内存的项目
  • 某种“未来”结构(例如 Task)可能在实际使用之前开始异步初始化,从而消耗空闲 CPU 时间,以防以后需要该结构的可能性很高

与此相反,Lazy 通常适用于以下情况

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

【讨论】:

    【解决方案3】:

    这不是一个负面的方面,而是懒惰的人的一个陷阱:)。

    惰性初始化器类似于静态初始化器。它们运行一次。如果抛出异常,则会缓存该异常,随后对 .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
        {
    
        }
    }
    

    【讨论】:

    • 谢谢@Thilak。这很有趣。我不知道异常是这样缓存的。
    • @Fuji 这就是 MEF 团队在 MEF 2 中添加 ExportFactory 和 ExportLifetimeContext 的原因。看看blogs.msdn.com/b/bclteam/archive/2011/11/17/…
    • 我想我现在需要回去修改我的一些 MEF 代码。 ;)
    • 这只是在某些时候是正确的:异常缓存的行为取决于发布方法。
    • 如果您想避免异常缓存,请考虑使用LazyWithNoExceptionCaching - stackoverflow.com/a/42567351/34092
    【解决方案4】:

    我开始使用Lazy&lt;T&gt; 主要是因为它在从数据库加载资源时具有并发能力。因此,我摆脱了锁定对象和可争论的锁定模式。 在我的情况下,ConcurrentDictionary + Lazy 作为一个价值让我很开心,这要感谢@Reed Copsey 和他的blog post

    如下所示。而不是调用:

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

    我们将改为使用 ConcurrentDictionary>,并且 写:

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

    到目前为止,Lazy&lt;T&gt; 没有任何缺点。

    【讨论】:

    • 这很甜蜜。不幸的是,我今天有点太活跃了,票数用完了,所以我不能对你的回答投赞成票。 ;*(
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      “整个应用程序”到底是什么意思?

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

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

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

      【讨论】:

        【解决方案7】:

        Lazy 用于在不需要时保留资源。这种模式很好,但实现起来可能没用。

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

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

        Lazy 是为了获得生产力的懒惰开发,但这种收益可能会因高使用率而丧失。

        如果你有一个真正透明的实现(例如使用代理模式),它会摆脱不利影响,并且在很多情况下都非常有用。

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

        【讨论】:

          猜你喜欢
          • 2011-01-15
          • 1970-01-01
          • 1970-01-01
          • 2011-03-13
          • 1970-01-01
          • 1970-01-01
          • 2011-03-28
          • 2016-02-21
          • 2011-07-02
          相关资源
          最近更新 更多