在用C#编码时,我经常喜欢用这种模式来做一个懒惰的getter属性。
private string _myProp;
string MyProp => _myProp ?? (_myProp = getMyProp());
我认为这是非常标准的C#实践。函数 getMyProp()
只是一些函数,计算并返回一个 string
. 它只被调用一次来初始化这个属性,从此以后它就被缓存了。我可以将它用于任何 object
我喜欢的类型,不只是 string
. 然而,当我尝试对一个原始类型(如int)进行操作时,我遇到了一个问题。例如,如果我尝试以下操作。
private int _operationCount;
int OperationCount => _operationCount ?? (_operationCount = GetOperationCountFromUser());
我在这里得到一个编译器错误 它说:"Error CS0019 Operator '?
错误 CS0019 操作符'??'不能应用于int和int类型的操作数。
我理解错误的意思是int是一个基元类型,所以我们不能对它应用空检查操作符,因为一个 int
永远不会是空的。但是有什么办法可以实现我在这里的要求吗?我正在寻找一个解决方案,有以下几点。
int
属性被初始化为函数调用的返回值。int
属性是从内存中加载的变量中获取的,即函数不会再被调用。最初的问题只是把 "懒惰 "作为编写其中一个属性的原因,但当我合作这个问题时,我意识到,可能我对上述模式的主要用例是使用了 ??
操作符是强制初始化。通过这种方式强制初始化一个属性,它总是在你第一次访问它时被初始化。意味着你不必担心将初始值注入多个构造函数--这一方面会使它们混乱,另一方面也不可靠,因为你可能会忘记在其中一个构造函数中初始化。
一个解决方案是使支持类型可为空,并将调用到 Value
属性的RHS。
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>
比使用nullable类型更好。有时你推迟初始化的对象可能无法使用--懒惰会返回 null
在这种情况下(例如,当你试图从数据库中获取一些东西,但没有值)。在这种情况下,nullable字段会告诉你什么?你可能最终会在每次属性访问时尝试初始化它。