【问题标题】:Concise way to writy Lazy loaded properties in C#在 C# 中编写延迟加载属性的简洁方法
【发布时间】:2018-11-12 23:36:09
【问题描述】:

当我想使用Lazy<T>并需要参考this时,我需要写很多样板代码:

// the private member
private Lazy<SubEventCollection> _SubEvents;

public Event()
{
    // needs to be initialized in the constructor because I refer to this
    _SubEvents = new Lazy<SubEventCollection3>(CreateSubEvents);
}

// the "core" body
private SubEventCollection CreateSubEvents()
{
    SubEventCollection3 collection;

    using ( var stream = new MemoryStream(DbSubEventsBucket) )
        collection = Serializer.Deserialize<SubEventCollection3>(stream);

    collection.Initialize(this);

    return collection;
}

// The final property
public SubEventCollection SubEvents => _SubEvents.Value;

这一切真的有必要吗?感觉就像太多的样板和所有的地方。有没有什么捷径读起来更好看,周围没有那么多单独的样板? 我可能可以将主体移到构造函数中,但我也不喜欢这样 - 即您将 很多 重要逻辑移到构造函数中。

我的首选方式是类似于 Knockout.js / TypeScript。

subEvents = ko.lazyComputed(() =>
{
    SubEventCollection3 sub_events;

    using ( var stream = new MemoryStream(DbSubEventsBucket) )
        sub_events = Serializer.Deserialize<SubEventCollection3>(stream);

    sub_events.Initialize(this);

    return sub_events;
})

这里没有很多“活动部件”,而且非常简洁。 还有哪些其他选择?我注意到我经常退回到手动的“懒惰”构造。

private SubEventCollection _SubEvents;

public SubEventCollection SubEvents
{
    get
    {
        if ( _SubEvents == null )
        {
            using ( var stream = new MemoryStream(DbSubEventsBucket) )
                collection = Serializer.Deserialize<SubEventCollection3>(stream);

            collection.Initialize(this);

            _SubEvents = collection;
        }

        return _SubEvents;
    }
}

至少这比 Lazy 方式具有更少的“移动部分”,并且我可以将所有内容放在一起(不必将一半的逻辑放在构造函数中)。当然这还有很多其他的缺点,比如它不是线程安全的。

我是否仍然缺少其他选择?

附言 我假设有两种不同的答案 - 一种用于真正的线程安全延迟加载,另一种用于简洁版本,您不在乎它是否会意外被调用两次。

【问题讨论】:

  • lazyComputed 与 C# 中的 Lazy 不同。例如,Lazy 最多运行一次(与 lazyComputed 不同,它会在依赖关系发生变化时重新运行)。
  • 我觉得这需要一些固执己见的回应。我经常使用manual惰性构造的相同方法。我认为这取决于情况:stackoverflow.com/questions/6847721/when-should-i-use-lazyt
  • 我认为您不会比原始代码更简洁。
  • 这一切真的有必要吗?感觉样板文件太多,到处都是”听起来像是代码强迫症和语法嫉妒的案例。如果你想使用 Lazy 你会被这个模式困住。此外,Knockout.js / TypeScript 是完全不同的野兽,它们可以做这些恶作剧是有根本原因的,而 CLR 在 .Net 中不允许这样做
  • 为了记录,我个人会使用LazyWithNoExceptionCaching 而不是Lazy - 但这不利于您的简洁。 stackoverflow.com/a/42567351/34092

标签: c# syntax lazy-loading boilerplate


【解决方案1】:

我建议将Lazy 从类的内部转移到如何在方法中使用类。在类的主体内急切地初始化Event(包括它的SubEventCollection),但不要在外面使用Event,而是使用Lazy&lt;Event&gt;

所以,声明:

public class Event 
{
    public SubEventCollection SubEvents { get; private set; }
    public Event()
    {
         using ( var stream = new MemoryStream(DbSubEventsBucket) )
             SubEvents = Serializer.Deserialize<SubEventCollection3>(stream);

         SubEvents.Initialize(this);
    }
}

但是,与其从产生事件的任何事物返回 Event,不如返回 Lazy&lt;Event&gt;,让他们能够根据需要返回更多数据。这还具有通知Event 的用户获取事件数据可能是一项昂贵的操作的优势。

【讨论】:

    【解决方案2】:

    目前我正在尝试自己的实现 - 仍需要审查。

    类:

    /// <summary>
    /// Warning: might not be as performant (and safe?) as the Lazy<T>, see: 
    /// https://codereview.stackexchange.com/questions/207708/own-implementation-of-lazyt-object
    /// </summary>
    public class MyLazy<T>
    {
        private T               _Value;
        private volatile bool   _Loaded;
        private object          _Lock = new object();
    
    
        public T Get(Func<T> create)
        {
            if ( !_Loaded )
            {
                lock (_Lock)
                {
                    if ( !_Loaded ) // double checked lock
                    {
                        _Value   = create();
                        _Loaded = true;
                    }
                }
            }
    
            return _Value;
        } 
    
    
        public void Invalidate()
        {
            lock ( _Lock )
                _Loaded = false;
        }
    }
    

    用途:

    MyLazy _SubEvents = new MyLazy();
    public SubEventCollection SubEvents => _SubEvents.Get(LoadSubEvents);
    
    private SubEventCollection LoadSubEvents()
    {
        using ( var stream = new MemoryStream(DbSubEventsBucket) )
        {
            var sub_event_collection = Serializer.Deserialize<SubEventCollection>(stream);
            sub_event_collection.Initialize(this);
    
            return sub_event_collection;
        }
    }
    

    优点:

    • 我可以将所有相关代码放在一起(不必将一半放在构造函数中)

    【讨论】:

    • “一半在构造函数中”? 9%,实际上,并且您引入了意外使用 MyLazy 实例和 create 函数的错误组合的风险。在我看来,绝对不值得。
    • 好的,样板代码的一半。然后是最关键的部分。我不喜欢这样(特别是如果你在那里有 +5 属性),但我尊重你有不同的意见。生活,让生活。对负面投票来说太糟糕了(如果实施是正确的),但是和平!
    猜你喜欢
    • 1970-01-01
    • 2014-10-02
    • 1970-01-01
    • 2017-12-28
    • 2013-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-26
    相关资源
    最近更新 更多