C#基础 - Lazy<T>

1. Lazy<T> 概述

往往有这样的情景,我们需要创建一个大对象,这样的对象往往需要较长的时间和较多的空间,为了避免在每次运行时创建这种家伙,在.NET Framework 4 后提供一种便捷的方式:Lazy<T>(称之为懒对象)。

2. Lazy<T> 的使用

我们就来看下Lazy如何使用。


[Serializable]
class Large
{
    public Large() { }
    public void Test()
    {
        Console.WriteLine("Test");
    }
}

上面代码定义了一个Large对象

class Program
{
    static void Main(string[] args)
    {
        Lazy<Large> lazyObject = new Lazy<Large>();
        Console.WriteLine(lazyObject.IsValueCreated);
        lazyObject.Value.Test();
        Console.WriteLine(lazyObject.IsValueCreated);
    }
}

Main函数中进行执行,运行结果如下:

False
Test
True

这个例子很简单,也是Lazy最基本,也是最常用的应用方式,Lazy<T>实现了按需延迟加载

3. 实现自己的Lazy<T>

在.NET Framework 4.0之前,来看看我们是怎么样定义Lazy的吧。

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了,没有线程安全检测,只有着访问时创建对象,可是对于我们一般的应用来说也许就已经足够了,大家也可以读下老赵写的 较为理想的延迟代理的编写方式

4. Lazy<T>的.NET Framework实现

其实.NET Framework和上面的实现大同小异,有两点主要的不同:

A. 引入了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>

B. 线程安全的控制

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

public enum LazyThreadSafetyMode
{
    None,
    PublicationOnly,
    ExecutionAndPublication
}

None Lazy<T> 实例不是线程安全的;如果从多个线程访问该实例,则其行为不确定。 仅应在高性能至关重要并且保证决不会从多个线程初始化 Lazy<T> 实例时才使用该模式。 如果您使用指定初始化方法(valueFactory 参数的 Lazy<T> 构造函数,并且如果首次调用 Lazy<T>.Value 属性时,该初始化方法引发异常(或者无法处理异常),则该异常将在 Lazy<T>.Value 属性的后续调用上再次缓存和引发该异常。 如果您使用不指定初始化方法的 Lazy<T> 构造函数,则不会缓存 T 默认构造函数引发的异常。 在这种情况下,随后调用 Lazy<T>.Value 属性可能会成功地初始化 Lazy<T> 实例。 如果初始化方法递归访问 Lazy<T> 实例的 Value 属性,则引发 InvalidOperationException。

PublicationOnly 当多个线程尝试同时初始化一个 Lazy<T> 实例时,允许所有线程都运行初始化方法(如果没有初始化方法,则为默认构造函数)。 完成初始化的第一个线程设置 Lazy<T> 实例的值。 该值将返回给同时运行初始化方法的所有其他线程,除非该初始化方法对这些线程引发异常。 争用线程创建的任何 T 实例都将被丢弃。 如果初始化方法对任何线程引发异常,则该异常会从在该线程上的Lazy<T>.Value 属性传播出去。 不缓存该异常。 IsValueCreated 属性的值仍然为 false,并且随后通过其中引发异常的线程或通过其他线程对 Value 属性的调用会导致初始化方法再次运行。 如果初始化方法递归访问 Lazy<T> 实例的 Value 属性,则不会引发异常。

ExecutionAndPublication 使用锁来确保只有一个线程可以在线程安全的方式下初始化 Lazy<T> 实例。 如果初始化方法(如果没有初始化方法,则为默认构造函数)在内部使用锁,则可能会发生死锁。 如果您使用指定初始化方法(valueFactory 参数的 Lazy<T> 构造函数,并且如果首次调用 Lazy<T>.Value 属性时,该初始化方法引发异常(或者无法处理异常),则该异常将在 Lazy<T>.Value 属性的后续调用上再次缓存和引发该异常。 如果您使用不指定初始化方法的 Lazy<T> 构造函数,则不会缓存 T 默认构造函数引发的异常。 在这种情况下,随后调用 Lazy<T>.Value 属性可能会成功地初始化 Lazy<T> 实例。 如果初始化方法递归访问 Lazy<T> 实例的 Value 属性,则引发 InvalidOperationException。

具体可参考下面链接 http://msdn.microsoft.com/en-us/library/system.threading.lazythreadsafetymode%28VS.100%29.aspx

Comment