C#中原始类型属性的延迟/强制初始化

问题描述 投票:0回答:2

通常在C#中编码时,我喜欢将此模式用于惰性getter属性:

private string _myProp;
string MyProp => _myProp ?? (_myProp = getMyProp());

我认为这是非常标准的C#做法。函数getMyProp()只是一些计算并返回string的函数。只需调用一次即可初始化属性,然后将其缓存。我可以将其用于任何我喜欢的object类型,而不仅仅是string。但是,当我尝试为诸如int之类的原始类型执行此操作时,我遇到了一个问题。例如,如果我尝试以下操作:

private int _operationCount;
int OperationCount => _operationCount ?? (_operationCount = GetOperationCountFromUser());

我在这里遇到编译器错误。它说:

错误CS0019运算符'??'不能应用于类型为'int'和'int'的操作数

我理解该错误意味着int是原始类型,因此我们无法对其应用null检查运算符,因为int永远不会为null。但是有什么办法可以在这里实现我想要的?我正在寻找以下解决方案:

  • 在第一次访问时,int属性被初始化为函数调用的返回值。
  • 在后续访问中,int属性是从内存中的已加载变量中检索的,即该函数不再被调用。

更新:为什么要这样写属性?

最初的问题只是引用“懒惰”作为编写这些属性之一的原因,但是当我在这个问题上进行协作时,我意识到使用??运算符的上述模式的主要用例可能是强制初始化。通过以这种方式强制初始化属性,将始终在您首次访问该属性时对其进行初始化。意味着您不必担心将初始值注入多个构造函数中,这一方面使它们混乱,而且也不可靠,因为您可能会忘记在其中一个构造函数中进行初始化。

c# primitive-types lazy-initialization
2个回答
2
投票

一种解决方案是使后备类型为空,并在RHS上调用Value属性:

private int? _operationCount;
public int OperationCount => _operationCount ?? (_operationCount = GetOperationCountFromUser()).Value;

这里是一个完整的工作解决方案,带有后备功能的虚拟实现:

using System;

public class Program
{
    public static void Main()
    {
        X x = new X();
        Console.WriteLine("Hello World " + x.OperationCount);
    }
}

class X
{
    private int? _operationCount;
    public int OperationCount => _operationCount ?? (_operationCount = GetOperationCountFromUser()).Value;
    private int GetOperationCountFromUser() => 38;
}   

[感谢juharr's注释,之所以可以这样解释,是因为当nullable ?? expressionnullable.GetValueOrDefault(expresion)时,编译器会将nullable转换为Nullable<T>。这解释了为什么RHS(默认值)必须是原始类型,而LHS是可为空的类型。


1
投票

我个人看不到通过Value访问惰性对象的任何坏处(至少它清楚地告诉您,获取值时可能会有延迟)。但是您甚至可以跳过此步骤,变得非常懒惰。不幸的是,您不能为两个第三方类型之间的转换定义隐式运算符,但可以继承Lazy<T>并为您的类型定义隐式转换:

public class VeryLazy<T> : Lazy<T>
{
    public VeryLazy(Func<T> valueFactory) : base(valueFactory) { }
    public static implicit operator T (VeryLazy<T> lazy) => lazy.Value;
}

使用变得非常懒惰-只要想使用operationCount,就可以使用int

class X
{
 private readonly VeryLazy<int> _operationCount = new VeryLazy(GetOperationCountFromUser);
 public int OperationCount => _operationCount; // implicit conversion to int
 private static int GetOperationCountFromUser() => 38;
}

但是我并不那么懒,而且我发现没有隐式转换的代码更易于阅读

class X
{
 private readonly Lazy<int> _operationCount = new Lazy(GetOperationCountFromUser);
 public int OperationCount => _operationCount.Value; // we see there can be delay
 private static int GetOperationCountFromUser() => 38;
}

Lazy<T>优于使用可为空的类型。有时,您延迟进行初始化的对象可能不可用-在这种情况下,懒惰会返回null(例如,当您尝试从数据库中获取某些东西,但没有任何价值时)。在这种情况下,可为空的字段会告诉您什么?您可能最终会尝试在每个属性访问上对其进行初始化。

© www.soinside.com 2019 - 2024. All rights reserved.