何时延迟加载
本文关键字:延迟加载 何时 | 更新日期: 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();
这将产生以下结果
这不是延迟加载。
延迟加载意味着你只在真正访问的时候加载值(这不会发生在初始化器中)
延迟加载是这样的:
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();
取消第一个循环的注释,看看有什么不同。非常有用的例子来理解延迟执行和延迟加载。