通常在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
属性是从内存中的已加载变量中检索的,即该函数不再被调用。最初的问题只是引用“懒惰”作为编写这些属性之一的原因,但是当我在这个问题上进行协作时,我意识到使用??
运算符的上述模式的主要用例可能是强制初始化。通过以这种方式强制初始化属性,将始终在您首次访问该属性时对其进行初始化。意味着您不必担心将初始值注入多个构造函数中,这一方面使它们混乱,而且也不可靠,因为您可能会忘记在其中一个构造函数中进行初始化。
一种解决方案是使后备类型为空,并在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 ?? expression
为nullable.GetValueOrDefault(expresion)
时,编译器会将nullable
转换为Nullable<T>
。这解释了为什么RHS(默认值)必须是原始类型,而LHS是可为空的类型。
我个人看不到通过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
(例如,当您尝试从数据库中获取某些东西,但没有任何价值时)。在这种情况下,可为空的字段会告诉您什么?您可能最终会尝试在每个属性访问上对其进行初始化。