【问题标题】:Is my approach to lazy loading flawed?我的延迟加载方法有缺陷吗?
【发布时间】:2009-04-24 17:41:05
【问题描述】:

平台:带有 Resharper 4.1、.NET 3.5 的 Visual Studio 2008 SP1

我有一个带有静态方法GetProperty<T> 的类,它懒惰地返回一个属性值。

private static T GetProperty<T>(T backingField, Func<T> factory) 
    where T : class 
{
    if (backingField == null)
        backingField = factory();
    return backingField;
}

但是当我使用上述方法返回一个属性时, 我收到两个警告,说未分配私有支持字段。 但只有在需要时才会分配它们。

这个警告可忽略吗?
-- 或者 --
我加载属性的方法有缺陷吗?

【问题讨论】:

  • 我相信您使用 FXCop 会得到同样的警告。

标签: c# resharper lazy-loading warnings


【解决方案1】:

你的方法有缺陷。要采用这种方法,您需要将 backingField 设为 ref 参数。

private static T GetProperty<T>(ref T backingField, Func<T> factory)

然后在GetProperty 上,通过ref _ImagXpressref _PdfXpress

您现在的做法只是为参数分配一个新值,而不是实际的支持字段。

【讨论】:

  • +1,尽管 ref 变量通常是代码异味。既然是私有的,至少refugliness被完全封装了。
  • 您是按值传递,这意味着如果您更改 VALUE(即引用变量实际指向的内容),您只会更新本地副本。您必须通过 REFERENCE 才能对 VALUE 进行更改传播回来。
  • ref 参数在功能上类似于双指针,它允许您替换整个对象引用。值参数在功能上类似于单个指针,重新分配参数 (backingField = factory()) 只会在方法范围内替换它。您可以操作值参数的状态,但不能操作其对堆上对象的引用。
  • C# 中的对象引用不是指针。它们明确地不是指针,并且有很大的不同。向参数添加 ref 使其更像是一个指针,这正是您在这种情况下想要的。
  • Jon Skeet 可以比我更好地回答这个问题:yoda.arachsys.com/csharp/parameters.html
【解决方案2】:

你的方法有缺陷。你的领域永远不会被设置为任何东西。 backingField 参数是在 GetProperty&lt;T&gt; 方法中设置的,但这不会更新您要传入的字段。您需要像这样传递带有 ref 关键字的参数:

private static T GetProperty<T>(ref T backingField, Func<T> factory)

【讨论】:

    【解决方案3】:

    正如我在 cmets 的另一个答案中所说,对 ref 参数的需求是代码异味。首先,如果你在一个方法中这样做,你就打破了单一职责原则,但更重要的是,代码只能在你的继承层次结构中重用。

    这里有一个模式可以推导出来:

    public class LazyInit<T>
        where T : class
    {
        private readonly Func<T> _creationMethod;
        private readonly object syncRoot;
        private T _instance;
    
        [DebuggerHidden]
        private LazyInit()
        {
            syncRoot = new object();
        }
    
        [DebuggerHidden]
        public LazyInit(Func<T> creationMethod)
            : this()
        {
            _creationMethod = creationMethod;
        }
    
        public T Instance
        {
            [DebuggerHidden]
            get
            {
                lock (syncRoot)
                {
                    if (_instance == null)
                        _instance = _creationMethod();
                    return _instance;
                }
            }
        }
    
        public static LazyInit<T> Create<U>() where U : class, T, new()
        {
            return new LazyInit<T>(() => new U());
        }
    
        [DebuggerHidden]
        public static implicit operator LazyInit<T>(Func<T> function)
        {
            return new LazyInit<T>(function);
        }
    }
    

    这允许你这样做:

    public class Foo
    {
        private readonly LazyInit<Bar> _bar1 = LazyInit<Bar>.Create<Bar>();
        private readonly LazyInit<Bar> _bar2 = new LazyInit<Bar>(() => new Bar("foo"));
    
        public Bar Bar1
        {
            get { return _bar1.Instance; }
        }
    
        public Bar Bar2
        {
            get { return _bar2.Instance; }
        }
    }
    

    【讨论】:

    • 似乎有点过度设计,但应该可以工作,而且肯定是一个创造性的解决方案!
    • 针对这个特定的解决方案进行了过度设计,但它提供了一种线程安全(相对)的惰性初始化方法,可以在任何地方重用。这是我使用的生产代码,它是重构惰性初始化代码多次迭代的结果。
    • @Michael Meadows:哇,我想我可以将 LazyInit 集成到我的代码中。感谢您提供解决问题的不同观点。我还没有考虑过为延迟加载创建一个全新的类。
    • 我只有一个建议。假设您推迟的昂贵操作实际上导致空值?它将在后续调用中不断得到评估。我参加了您的课程并添加了一个私有布尔“已初始化”变量,而不是依赖于比较 _instance 与 null。它现在代表我的 v3.5 项目中的 .Net v4.0 Lazy 类。谢谢。
    【解决方案4】:

    提出不同的解决方案:

    我想说的是,通过将该逻辑封装到一个单独的方法中,您并没有节省很多。您正在增加复杂性而没有太多收获。我建议这样做:

    protected PdfXpress PdfXpress
    {
        get
        {
            if (_PdfXpress == null)
                _PdfXpress = PdfXpressSupport.Create();
    
            return _PdfXpress;
        }
    }
    
    protected ImagXpress ImagXpress
    {
        get
        {
            if (_ImagXpress == null)
                _ImagXpress = IMagXpressSupport.Create();
    
            return _ImagXpress;
        }
    }
    

    您添加了几行代码,但大大降低了复杂性。

    【讨论】:

    • 那是我最初的实现;但我必须添加更多属性,所以我最终重构了一个方法来封装“null”检查。
    • dance:存在过度设计之类的东西 :) 我非常赞成这种方法,因为它更清楚发生了什么。
    • @Adam:我现在开始看到,我倾向于重构/过度设计太多......
    猜你喜欢
    • 1970-01-01
    • 2010-10-25
    • 1970-01-01
    • 2011-04-29
    • 2011-09-20
    • 1970-01-01
    • 2018-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多