Lazy<T>解决什么问题?

1、大对象加载

  考虑下面的需求,有个对象很大,创建耗时,并且要在托管堆上分配一大块空间。我们当然希望,用到它的时候再去创建。也就是延迟加载,等到真正需要它的时候,才去加载。

  显然,这里需要加一个中间层,将大对象封装起来,暴露接口,开始并不创建大对象,等到用户真正访问对象的时候,再去创建。另外,这个中间层应该可以封装不同类型的大对象,因此需要类模版。Lazy<T>就是为了解决这个问题。

  典型的使用

public Lazy<AccountService> AccountServ = new Lazy<AccountService>();
public Lazy<ProductService> ProductService = new Lazy<ProductService>();

2、将委托或者方法对象保存,并在需要的时候调用。

private readonly Lazy<IDbConnection> _connectionLazy;
public CallHistoryRepository(ConnectionFactory connectionFactory)
{
_connectionLazy = new Lazy<IDbConnection>(()=>connectionFactory.Connection);
}

一旦使用.Vale,那么对应的变量就会被实例化,IsValueCreated属性也就变成了true。

实现自己的Lazy<T>

  在.NET Framework 4.0之前,大对象就是存在的,那么对于一个大型系统而言,怎么样对付一个大对象呢。主要有两点:延迟加载即时清理。前者解决创建问题,后者解决回收问题。

  那么在来看Lazy<T>的.NET Framework实现之前,我们先来自己实现一个简单的Lazy<T>吧。

class MyLazy<T> where T : new()
{
private T value;
private bool isLoaded;
public MyLazy()
{
isLoaded = false;
}
public T Value
{
get
{
if (!isLoaded)
{
value = new T();
isLoaded = true;
}
return value;
}
}
}

这应该是最简单版本的Lazy<T>了,没有线程安全检测,只有着访问时创建真实对象,可是对于我们一般的应用来说也许就已经足够了。

.NET Lazy<T> 实现

.NET Core和我们的实现,有两点主要的不同:

1、 引入了Boxed内部类:

[Serializable]
private class Boxed
{
// Fields
internal T m_value; // Methods
[TargetedPatchingOptOut("Performance critical to inline this type of method across NGen image boundaries")]
internal Boxed(T value)
{
this.m_value = value;
}
}

  该内部类取代了我在上面实现中的泛型约束,使之更通用。

  但是我们也应该注意到,如果T为结构体,那么由于T很大,所以装箱拆箱反而也许是个更耗费效率的事情,因此,个人建议,对值类型慎用Lazy<T>

2、 线程安全的控制

在线程安全的控制选项中,.NET Framework为我们提供了这样的枚举选项:

public enum LazyThreadSafetyMode
{
None,
PublicationOnly,
ExecutionAndPublication
}

默认值为ExecutionAndPublication

枚举选项MSDN介绍如下

http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode%28VS.100%29.aspx

isThreadSafe则应用于多线程环境下,如果isThreadSafe为false,那么延迟加载对象则一次只能创建于一个线程。

Lazy<T>源码

System.Runtime命名空间下

由于core中的lazy源码我只找到了下图这个,再往下lazy的实现并没找

所以我使用.Net 4.5的lazy源码

一、最常使用的属性Value

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public T Value
{
get
{
Boxed boxed = null;
if (m_boxed != null )
{
// Do a quick check up front for the fast path.
boxed = m_boxed as Boxed;
if (boxed != null)
{
return boxed.m_value;
}
LazyInternalExceptionHolder exc = m_boxed as LazyInternalExceptionHolder;
Contract.Assert(m_boxed != null);
exc.m_edi.Throw();
}
return LazyInitValue();
}
}
//null --> value is not created
//m_value is Boxed --> the value is created, and m_value holds the value
//m_value is LazyExceptionHolder --> it holds an exception
private object m_boxed;

如果m_boxed有值,就直接装箱返回对应值(这里就要注意值类型装箱的性能损失了)。这个装箱是为了检验下m_boxed的值,因为其中有可能是异常。

二、LazyInitValue方法

如果m_boxed为空,就调用LazyInitValue方法,这里有针对线程安全模式的判断

/// <summary>
/// local helper method to initialize the value
/// </summary>
/// <returns>The inititialized T value</returns>
private T LazyInitValue()
{
Boxed boxed = null;
LazyThreadSafetyMode mode = Mode;
if (mode == LazyThreadSafetyMode.None)
{
boxed = CreateValue();
m_boxed = boxed;
}
else if (mode == LazyThreadSafetyMode.PublicationOnly)
{
boxed = CreateValue();
if (boxed == null ||
Interlocked.CompareExchange(ref m_boxed, boxed, null) != null)
{
// If CreateValue returns null, it means another thread successfully invoked the value factory
// and stored the result, so we should just take what was stored. If CreateValue returns non-null
// but we lose the ---- to store the single value, again we should just take what was stored.
boxed = (Boxed)m_boxed;
}
else
{
// We successfully created and stored the value. At this point, the value factory delegate is
// no longer needed, and we don't want to hold onto its resources.
m_valueFactory = ALREADY_INVOKED_SENTINEL;
}
}
else
{
object threadSafeObj = Volatile.Read(ref m_threadSafeObj);
bool lockTaken = false;
try
{
if (threadSafeObj != (object)ALREADY_INVOKED_SENTINEL)
Monitor.Enter(threadSafeObj, ref lockTaken);
else
Contract.Assert(m_boxed != null); if (m_boxed == null)
{
boxed = CreateValue();
m_boxed = boxed;
Volatile.Write(ref m_threadSafeObj, ALREADY_INVOKED_SENTINEL);
}
else // got the lock but the value is not null anymore, check if it is created by another thread or faulted and throw if so
{
boxed = m_boxed as Boxed;
if (boxed == null) // it is not Boxed, so it is a LazyInternalExceptionHolder
{
LazyInternalExceptionHolder exHolder = m_boxed as LazyInternalExceptionHolder;
Contract.Assert(exHolder != null);
exHolder.m_edi.Throw();
}
}
}
finally
{
if (lockTaken)
Monitor.Exit(threadSafeObj);
}
}
Contract.Assert(boxed != null);
return boxed.m_value;
}

三、CreateValue方法

返回一个实例对象T,采用传入的func方法,这里会对是否已经返回做出判断,如果已经返回,就返回null

/// <summary>Creates an instance of T using m_valueFactory in case its not null or use reflection to create a new T()</summary>
/// <returns>An instance of Boxed.</returns>
private Boxed CreateValue()
{
Boxed boxed = null;
LazyThreadSafetyMode mode = Mode;
if (m_valueFactory != null)
{
try
{
// check for recursion
if (mode != LazyThreadSafetyMode.PublicationOnly && m_valueFactory == ALREADY_INVOKED_SENTINEL)
throw new InvalidOperationException(Environment.GetResourceString("Lazy_Value_RecursiveCallsToValue"));
Func<T> factory = m_valueFactory;
if (mode != LazyThreadSafetyMode.PublicationOnly) // only detect recursion on None and ExecutionAndPublication modes
{
m_valueFactory = ALREADY_INVOKED_SENTINEL;
}
else if (factory == ALREADY_INVOKED_SENTINEL)
{
// Another thread ----d with us and beat us to successfully invoke the factory.
return null;
}
boxed = new Boxed(factory());
}
catch (Exception ex)
{
if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode
m_boxed = new LazyInternalExceptionHolder(ex);
throw;
}
}
else
{
try
{
boxed = new Boxed((T)Activator.CreateInstance(typeof(T))); }
catch (System.MissingMethodException)
{
Exception ex = new System.MissingMemberException(Environment.GetResourceString("Lazy_CreateValue_NoParameterlessCtorForT"));
if (mode != LazyThreadSafetyMode.PublicationOnly) // don't cache the exception for PublicationOnly mode
m_boxed = new LazyInternalExceptionHolder(ex);
throw ex;
}
}
return boxed;
}

Core源码(三) Lazy<T>的更多相关文章

  1. 一起来看CORE源码(一) ConcurrentDictionary

    先贴源码地址 https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Col ...

  2. 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器

    1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...

  3. ASP.NET Core[源码分析篇] - Authentication认证

    原文:ASP.NET Core[源码分析篇] - Authentication认证 追本溯源,从使用开始 首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务 ...

  4. ASP.NET Core源码学习(一)Hosting

    ASP.NET Core源码的学习,我们从Hosting开始, Hosting的GitHub地址为:https://github.com/aspnet/Hosting.git 朋友们可以从以上链接克隆 ...

  5. asp.net core源码地址

    https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet 这个下面是asp.net core 框架 ...

  6. ASP.NET Core[源码分析篇] - WebHost

    _configureServicesDelegates的承接 在[ASP.NET Core[源码分析篇] - Startup]这篇文章中,我们得知了目前为止(UseStartup),所有的动作都是在_ ...

  7. ASP .NET CORE 源码地址

    ASP .NET CORE 源码地址:https://github.com/dotnet/ 下拉可以查找相应的源码信息, 例如:查找 ASP .NET CORE Microsoft.Extension ...

  8. DOTNET CORE源码分析之IOC容器结果获取内容补充

    补充一下ServiceProvider的内容 可能上一篇文章DOTNET CORE源码分析之IServiceProvider.ServiceProvider.IServiceProviderEngin ...

  9. AQS源码三视-JUC系列

    AQS源码三视-JUC系列 前两篇文章介绍了AQS的核心同步机制,使用CHL同步队列实现线程等待和唤醒,一个int值记录资源量.为上层各式各样的同步器实现画好了模版,像已经介绍到的ReentrantL ...

随机推荐

  1. OverLoad怎么用

    首先给出一个实例 package practice3; public class TryOverLoad { public static void main(String[] args) { Syst ...

  2. 【java】项目中的类名改变

    如果已经开发了一段时间,那如何一次性修改所有文件中的该类类名呢,一个个改太麻烦了还浪费时间,匹配关键字又怕误伤. 选中项目中需要改变的类,按F2键 重命名后eclipse会自动修改.

  3. cocoscreator查找节点的方法 (跟jquery find一样)

    var each = function(object, callback) { var type = (function() { switch (object.constructor) { case ...

  4. RabbitMQ与Spring的框架整合之Spring Cloud Stream实战

    1.RabbitMQ与Spring Cloud Stream整合实战.SpringCloud Stream整体结构核心概念图,如下所示: 图示解释:Outputs输出,即消息的发送端.Inputs输入 ...

  5. 理解并运用TP5.1-Facade

    1.内容介绍 深入解析tp5.1与laravel 中Facade底层原理实现 1. 什么是Facade 2. 为什么需要有什么好处 3.  Facade实现原理 4. 功能实现. 5. 容器注入 2. ...

  6. jQuery从零开始(三)-----ajax

    jq当中的ajax技术 $.ajax $.get() $.post() $.load() $.ajax({ url:'请求文件的地址', type:'请求文件使用的方法', data:'向请求的api ...

  7. Azkaban(3.x)编译安装使用

    官网地址:https://azkaban.readthedocs.io Azkaban 有三种部署方式:单服务模式.2个服务模式.分布式多服务模式 简单实用仅需单服务模式即可 2个服务模式,需要配置m ...

  8. [Linux] ubuntu环境安装和使用elasticsearch

    wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -apt-get install ap ...

  9. c/c++概述

    c/c++的学习分为两个部分 一.语言标准 语言标准定义了功能特性和标准库两部分. 功能特性由编译器负责具体实现,比如linux下gcc,windows下Visual Studio 标准库实现依赖于具 ...

  10. 2019面向对象程序设计(Java) 第16周学习指导及要求

    2019面向对象程序设计(Java)第16周学习指导及要求 (2019.12.13-2019.12.16) 学习目标 (1) 掌握Java应用程序的打包操作: (2) 掌握线程概念: (3) 掌握线程 ...