我最近在.Net中了解了Lazy类,并且可能过度使用它了。我在下面有一个例子,可以用热切的方式对事情进行评估,但如果反复调用会导致重复相同的计算。在这个特定的例子中,使用Lazy的成本可能无法证明其好处,我不确定这一点,因为我还不了解lambdas和lazy调用的成本。我喜欢使用链式Lazy属性,因为我可以将复杂的逻辑分解为小的,可管理的块。我也不再需要考虑初始化东西的最佳位置 - 我需要知道的是,如果我不使用它们将不会初始化,并且在我开始使用它们之前将被初始化一次。但是,一旦我开始使用lazy和lambdas,那么简单的类现在更加复杂。我不能客观地决定何时这是合理的,并且在复杂性,可读性,可能速度方面这是一个过度杀伤力。你的一般建议是什么?
// This is set once during initialization.
// The other 3 properties are derived from this one.
// Ends in .dat
public string DatFileName
{
get;
private set;
}
private Lazy<string> DatFileBase
{
get
{
// Removes .dat
return new Lazy<string>(() => Path.GetFileNameWithoutExtension(this.DatFileName));
}
}
public Lazy<string> MicrosoftFormatName
{
get
{
return new Lazy<string>(() => this.DatFileBase + "_m.fmt");
}
}
public Lazy<string> OracleFormatName
{
get
{
return new Lazy<string>(() => this.DatFileBase + "_o.fmt");
}
}
答案 0 :(得分:2)
你说“我不再需要考虑初始化内容的最佳位置”。
这是一个习惯性的坏习惯。您应该确切知道程序中发生了什么
当有一个需要传递的对象但需要一些计算时,你应该懒惰&lt;&gt; 。 因此,只有在使用它时才会计算出来。
除此之外,您还需要记住,使用lazy检索的对象是不在请求时处于程序状态的对象。
只有在使用对象时才能获得对象。如果你得到对程序状态很重要的对象,那么以后很难调试。
答案 1 :(得分:2)
这可能有点矫枉过正。
当泛型类型的创建或评估代价昂贵时,和/或在依赖类的每次使用中并不总是需要泛型类型时,通常应该使用Lazy。
更有可能的是,在调用你的getter时,任何调用你的getter的东西都需要一个实际的字符串值。在这种情况下返回Lazy是不必要的,因为调用代码将立即简单地评估Lazy实例以获得它真正需要的内容。 Lazy的“及时”性质在这里浪费了,因此,YAGNI(你不需要它)。
也就是说,Lazy固有的“开销”并不是那么多。 Lazy只是一个引用lambda的类,它将生成泛型类型。 Lambdas定义和执行相对便宜;它们只是方法,在编译时由CLR给出mashup名称。额外阶级的实例化是主要的踢球者,即便如此,它并不可怕。但是,从编码和性能角度来看,这是不必要的开销。
答案 2 :(得分:1)
这似乎没有使用Lazy<T>
来保存昂贵对象的创建/加载,因为它(可能是无意中)包装一些任意委托以延迟执行。您可能希望/打算派生的属性getter返回的是string
,而不是Lazy<string>
对象。
如果调用代码看起来像
string fileName = MicrosoftFormatName.Value;
然后显然没有意义,因为你立即“懒惰加载”。
如果调用代码看起来像
var lazyName = MicrosoftFormatName; // Not yet evaluated
// some other stuff, maybe changing the value of DatFileName
string fileName2 = lazyName.Value;
然后您可以看到fileName2
对象创建后lazyName
有可能无法确定。
在我看来,Lazy<T>
并非最适合公共财产;在这里,你的getter正在返回新的(如全新的,不同的,额外的)Lazy<string>
个对象,因此每个调用者将(可能)获得不同的 .Value
!您的所有Lazy<string>
属性都取决于首次访问DatFileName
时设置的.Value
,因此 总是需要考虑何时初始化相对于每个派生属性的使用。
请参阅MSDN文章“Lazy Initialization”,该文章创建了一个私有Lazy<T>
支持变量和一个公共属性getter,如下所示:
get { return _privateLazyObject.Value; }
我可能会猜测您的代码应该/可能会使用Lazy<string>
来定义您的“set-once”基本属性:
// This is set up once (durinig object initialization) and
// evaluated once (the first time _datFileName.Value is accessed)
private Lazy<string> _datFileName = new Lazy<string>(() =>
{
string filename = null;
//Insert initialization code here to determine filename
return filename;
});
// The other 3 properties are derived from this one.
// Ends in .dat
public string DatFileName
{
get { return _datFileName.Value; }
private set { _datFileName = new Lazy<string>(() => value); }
}
private string DatFileBase
{
get { return Path.GetFileNameWithoutExtension(DatFileName); }
}
public string MicrosoftFormatName
{
get { return DatFileBase + "_m.fmt"; }
}
public string OracleFormatName
{
get { return DatFileBase + "_o.fmt"; }
}
答案 3 :(得分:0)
使用Lazy
创建简单的字符串属性确实是一种过度杀伤力。使用lambda参数初始化Lazy
实例可能比执行单个字符串操作要昂贵得多。还有一个其他人没有提到的重要论点 - 请记住 lambda参数由编译器解析为非常复杂的结构,远比字符串连接更复杂。
答案 4 :(得分:0)
使用延迟加载的另一个区域是可以在部分状态下使用的类型。例如,请考虑以下事项:
public class Artist
{
public string Name { get; set; }
public Lazy<Manager> Manager { get; internal set; }
}
在上面的示例中,消费者可能只需要使用我们的Name属性,但是必须填充可能使用或不使用的字段可能是延迟加载的地方。我说可以不应该,因为总有一些情况可能会更高效地加载所有前端......取决于你的应用程序需要做什么。