何时延迟加载

本文关键字:延迟加载 何时 | 更新日期: 2023-09-27 18:11:05

我延迟加载所有成员。我这样做已经有一段时间了,从表面上看,我只是认为延迟加载是一件好事。

假设我们有

public class SomeClass
{
   public int anInt;
   public SomeReferenceType member1;
   public SomeClass()
   {
      //initialize members in constructor when needed (lazy load)
      anInt = new int();
      member1 = new SomeReferenceType();
   }
}
这样做有什么缺点吗?这是一个合适的延迟加载模式吗?延迟加载值类型是否有意义(对于现代RAM来说,这甚至很重要)?


从你的回答中了解到这些,我想知道上述与这之间是否有什么不同。

public class SomeClass
    {
       public int anInt;
       public SomeReferenceType member1 = new SomeReferenceType();
       public SomeClass()
       {
       }
    }

何时延迟加载

首先,在构造函数内部初始化成员并不是惰性加载。

延迟加载是在第一次请求成员时初始化成员。.NET中的一个简单示例(使用一些双重检查锁定,这样我们就不会有线程问题):

public class SomeClass
{
    private object _lockObj = new object();
    private SomeReferenceType _someProperty;
    public SomeReferenceType SomeProperty
    {
        get
        {
            if(_someProperty== null)
            {
                lock(_lockObj)
                {
                    if(_someProperty== null)
                    {
                        _someProperty= new SomeReferenceType();
                    }
                }
            }
            return _someProperty;
        }
        set { _someProperty = value; }
    }
}

幸运的是,如果你使用的是。net 4,你现在可以使用Lazy<T>类来为你处理这些问题,使事情变得容易得多。

其次,当你有很多成员,这些成员的加载成本可能很高,并且你确定你将使用所有这些值时,延迟加载是一个好主意。该成本将导致类型的实例化速度不必要地慢。

为了延迟加载而延迟加载会给代码增加不必要的复杂性,如果操作不当(例如,在处理线程时)可能会导致后续问题。

这不是真正的延迟加载。这是在构造上初始化。在惰性加载中,我们通常的意思是在项第一次被引用时构造它。

    private string _someField;
    public string SomeField
    {
        get 
        {
            // we'd also want to do synchronization if multi-threading.
            if (_someField == null)
            {
                _someField = new String('-', 1000000);
            }
            return _someField;
        }
    }

过去,惰性加载的一种典型方法是检查,锁定,检查,这样如果它已经创建,你就不会锁定,但由于有可能有两个项目通过检查并等待锁定,所以你在锁定中再次检查:

public class SomeClass
{
    private string _someField;
    private readonly object _lazyLock = new object();

    public string SomeField
    {
        get 
        {
            // we'd also want to do synchronization if multi-threading.
            if (_someField == null)
            {
                lock (_lazyLock)
                {
                    if (_someField == null)
                    {
                        _someField = new String('-', 1000000);
                    }
                }
            }
            return _someField;
        }
    }
}
有很多方法可以做到这一点,事实上,在。net 4.0中,有一个Lazy<T>类型可以帮助你轻松地实现线程安全的延迟加载。
public class SomeClass
{
    private readonly Lazy<string> _someField = new Lazy<string>(() => new string('-', 10000000), true);
    private readonly object _lazyLock = new object();

    public string SomeField
    {
        get
        {
            return _someField.Value;
        }
    }
}

至于为什么,如果您正在创建的对象趋于昂贵(内存或时间)并且不能保证您将需要它,那么延迟加载通常是一个好方案。如果您有理由确信它将始终被使用,那么您应该直接构造它。

从我看到的代码,你没有做延迟加载。在构造函数中初始化成员,这在实例的生命周期中总是会发生,而且很快就会发生。

因此,我想知道,对你来说,什么是非惰性加载?

延迟加载通常是在你访问某个对象时才初始化它。

这里有一个例子,使用。net 4.0的Lazy类,它可以帮助你做到这一点,延迟加载:

public class Foo
{
    private Lazy<int> _value = new Lazy<int>(() => 3);
    public int Value { get { return _value.Value; } }
}

关于线程安全-你可以传递第二个参数LazyThreadSafetyMode,它知道两种方式来指定线程安全:一种是初始化方法的执行可能会发生几次,但所有线程都得到第一次创建的值,或者一种执行也被保护不被运行几次。

使用/不使用延迟加载 示例

考虑一个作者可能有0到多本书

没有延迟加载:

public class Author
{
public int Id {get;set;}
public string Name  {get;set;}
public Icollecation<Book> Books {get;set;}
}
public class Book
{
public int Id {get;set;}
public string Title{get;set;}
public Author Author {get;set;}
public int yearOfrelease {get;set;}
}
// Get the info from books, consider my view has book name, title, author name
await _context.Books.ToListAsync();

这将产生以下结果

<表类>的书名字标题作者 年tbody> <<tr> Book1 td> Title1 td> 2022 Book2 td> Title2 td> 2021 tbody> /div>

这不是延迟加载。

延迟加载意味着你只在真正访问的时候加载值(这不会发生在初始化器中)

延迟加载是这样的:

private SomeClass _someRef = null;
public SomeClass SomeRef
{
  get
  {
    if(_someRef == null)
    {
       //initialisation just in case of access
       _someRef = new  SomeClass();
    }
    return _someRef;
  }
}

int类型的延迟加载属性应该是这样的:

private int? _heavyLoadedInt;
public int HeavyLoading
{
    get
    {
        if (_heavyLoadedInt == null)
            _heavyLoadedInt = DoHeavyLoading();
        return _heavyLoadedInt.Value;
    }
}

现在如果你看一下这个,你会发现这里有一些开销:你必须将值存储在一个空值(额外的内存)中;在每次访问时检查null,并在每次访问时从可空值中检索值。

如果你的整数确实需要一些非常繁重的计算,那么这种结构是有意义的。但是new int()不是一个繁重的计算,它只是返回0。开销很小,但是如果将此开销添加到更小的操作(即读取整数)中,就没有意义了。

当创建对象的成本非常高且很少使用对象时,延迟加载是必要的。因此,这就是值得实现延迟加载的场景。延迟加载的基本思想是在需要的时候加载对象/数据

延迟加载是一个概念,我们延迟对象的加载,直到我们需要它的点。简单来说,就是按需加载对象,而不是加载不必要的对象。

例如,考虑下面的例子,我们有一个简单的Customer类,这个Customer类里面有许多Order对象。仔细看一下Customer类的构造函数。当创建Customer对象时,它同时也加载Order对象。因此,即使我们需要或不需要Order对象,它仍然被加载。

链接到示例

List<int> number = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        var result = number.Where(x => x % 2 == 0);
        number.Add(20);
        //foreach (int item in result)
        //{
        //    Console.WriteLine("loop1:" + item);
        //}
        foreach (int item in result)
        {
            if (item == 4)
                break;
            Console.WriteLine("loop2:" + item);
        }
        number.Add(40);
        foreach (int item in result)
        {
            Console.WriteLine("loop3:"+item);
        }
        Console.ReadLine();

取消第一个循环的注释,看看有什么不同。非常有用的例子来理解延迟执行和延迟加载。