C#中的延迟初始化

延迟初始化或“延迟”初始化是一种访问对象的方法,它隐藏在一种机制中,该机制使您可以延迟创建该对象,直到首次访问该对象为止。出于各种原因,可能会出现延迟初始化的需求:从减少应用程序启动时的负载的需求到优化很少使用的功能。确实,并非总是使用应用程序的所有功能,而且立即使用了所有功能,因此将实现它们的对象的创建推迟到更好的时机是相当合理的。我想回顾一下C#中可用的惰性初始化选项。



为了演示示例,我将使用Test类,该类具有BlobData属性,该属性返回Blob类型的对象,根据传说,该对象的创建速度相当慢,因此决定懒惰地创建它。



class Test
{
    public Blob BlobData
    {
        get
        {
            return new Blob();
        }
    }
}




检查空值



该语言的第一个版本提供的最简单的选择是创建一个未初始化的变量,并在返回之前测试它是否为null。如果变量为null,则创建一个对象并将其分配给该变量,然后将其返回。在重复访问时,该对象将已经创建,我们将立即将其返回。



class Test
{
    private Blob _blob = null;

    public Blob BlobData
    {
        get
        {
            if (_blob == null)
            {
                _blob = new Blob();
            }

            return _blob;
        }
    }
}




首次访问该属性时,将在此处创建Blob类型的对象。或者,如果出于某种原因该程序在此会话中不需要它,则不会创建它。



三元运算符?:



C#具有三元运算符,可让您测试条件,如果为true,则返回一个值,如果为false,则返回另一个值。我们可以使用它来缩短和简化代码。



class Test
{
    private Blob _blob = null;

    public Blob BlobData
    {
        get
        {
            return _blob == null
                ? _blob = new Blob()
                : _blob;
        }
    }
}




本质保持不变。如果对象未初始化,则初始化并返回。如果已经初始化,那么我们立即将其返回。



一片空白



情况有所不同,例如,我们可能遇到其中Blob类具有重载==运算符的情况。为此,我们可能需要执行is null检查而不是== null。提供该语言的最新版本。



return _blob is null
    ? _blob = new Blob()
    : _blob;




但这是一个很小的题外话。



空合并运算符??



二进制运算符??将帮助我们进一步简化代码。

他的工作实质如下。如果第一个操作数不为null,则将其返回。如果第一个操作数为null,则返回第二个。



class Test
{
    private Blob _blob = null;

    public Blob BlobData
    {
        get
        {
            return _blob ?? (_blob = new Blob());
        }
    }
}




由于操作的优先级,第二个操作数必须用括号括起来。



运算子?? =



C#8引入了一个空分词赋值运算符,它看起来像是?? =

它的工作方式如下。如果第一个操作数不为null,则简单地返回它。如果第一个操作数为null,则将第二个操作数的值分配给它,并返回该值。



class Test
{
    private Blob _blob = null;

    public Blob BlobData
    {
        get
        {
            return _blob ??= new Blob();
        }
    }
}




这使我们可以将代码缩短一些。





如果有多个线程可以一次访问给定资源的可能性,则应使它成为线程安全的。否则,可能会发生这样的情况,例如,两个线程都将检查对象是否为null,结果将为false,然后将创建两个Blob对象,从而将系统加载所需的两倍,此外,其中之一对象将被保存,第二个将丢失。



class Test
{
    private readonly object _lock = new object();
    private Blob _blob = null;

    public Blob BlobData
    {
        get
        {
            lock (_lock)
            {
                return _blob ?? (_blob = new Blob());
            }
        }
    }
}




在执行某些语句之前,lock语句获取对指定对象的互斥锁定,然后释放该锁定。等效于使用System.Threading.Monitor.Enter(...,...);



懒惰<T>



.NET 4.0引入了Lazy类,以将所有这些脏活隐藏在我们眼前。现在我们只能保留Lazy类型的局部变量。访问其Value属性时,我们将获得Blob类的对象。如果对象是较早创建的,它将立即返回,否则,将首先创建。



class Test
{
    private readonly Lazy<Blob> _lazy = new Lazy<Blob>();

    public Blob BlobData
    {
        get
        {
            return _lazy.Value;
        }
    }
}




由于Blob类具有无参数的构造函数,因此Lazy可以在适当的时间创建它而没有任何问题。如果在创建Blob对象期间需要执行一些其他操作,则Lazy类的构造函数可以引用Func <T>



private Lazy<Blob> _lazy = new Lazy<Blob>(() => new Blob());




另外,在构造函数的第二个参数中,我们可以指示是否需要线程安全(相同的锁)。



属性



现在,让我们减少readonly属性,因为现代C#允许您很好地执行此操作。最后,一切看起来像这样:



class Test
{
    private readonly Lazy<Blob> _lazy = new Lazy<Blob>();
    public Blob BlobData => _lazy.Value;
}




惰性启动器



还有一个选项不是将类包装在Lazy包装器中,而是使用LazyInitializer功能。此类具有一个静态方法确保初始化,带有一堆重载,使您可以创建任何东西,包括线程安全和编写自定义代码以创建对象,但是其主要本质如下。检查对象是否未初始化。如果不是,则进行初始化。返回一个对象。使用此类,我们可以像这样重写代码:



class Test
{
    private Blob _blob;
    public Blob BlobData => LazyInitializer.EnsureInitialized(ref _blob);
}




就这样。感谢您的关注。



All Articles