在这种情况下,. net Lazy类的使用是不是有点过头了?
本文关键字:是不是 这种情况下 net Lazy | 更新日期: 2023-09-27 18:10:46
我最近才了解到。net中的Lazy类,可能一直在过度使用它。我下面有一个例子,其中的东西可以以渴望的方式进行评估,但如果反复调用,将导致重复相同的计算。在这个特定的示例中,使用Lazy的成本可能与收益不相称,我不能确定这一点,因为我还不明白lambda和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");
}
}
这可能有点过分了。
Lazy通常应该在泛型类型的创建或求值成本很高,和/或在依赖类的每次使用中并不总是需要泛型类型时使用。
更有可能的是,在调用getter时,任何调用getter的东西都需要一个实际的字符串值。在这种情况下返回Lazy是不必要的,因为调用代码将简单地立即计算Lazy实例以获得它真正需要的东西。Lazy的"及时"特性在这里被浪费了,因此,YAGNI(你不需要它)。
也就是说,Lazy固有的"开销"并不是那么多。Lazy只不过是一个引用lambda的类,它将生成泛型类型。Lambdas的定义和执行相对便宜;它们只是方法,在编译时CLR会给它们一个mashup名称。额外类的实例化是主要的问题,即使这样也不可怕。然而,从编码和性能的角度来看,这都是不必要的开销。
你说"我不再需要考虑在哪里初始化东西是最好的"
这是一个不好的习惯。你应该确切地知道程序中发生了什么
当有一个对象需要传递,但是需要一些计算时,你应该Lazy<>。因此,只有当它被使用时,它才会被计算。 除此之外,您还需要记住,使用lazy方法检索的对象是,而不是在请求时处于程序状态的对象。只有在使用对象时,才会得到对象本身。如果您得到的对象对程序的状态很重要,那么以后将很难调试。
这似乎不是使用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;
那么您可以看到,当创建lazyName
对象时,fileName2
有可能无法确定。
在我看来,Lazy<T>
不是最好的公共属性;这里,你的getter返回新的(如全新的,不同的,额外的)Lazy<string>
对象,所以每个调用者将(潜在地)得到一个不同的 .Value
!所有的Lazy<string>
属性都依赖于DatFileName
在它们的.Value
被第一次访问时被设置,所以你将总是需要考虑相对于每个派生属性的使用,它何时被初始化。
请参阅MSDN文章"延迟初始化",该文章创建了一个私有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"; }
}
使用Lazy
创建简单的字符串属性确实是多余的。使用lambda参数初始化Lazy
的实例可能比使用单个字符串操作要昂贵得多。还有一个其他人没有提到的重要参数——请记住,lambda参数被编译器解析为相当复杂的结构,比字符串连接复杂得多。
另一个适合使用延迟加载的领域是可以在局部状态下使用的类型。例如,考虑以下内容:
public class Artist
{
public string Name { get; set; }
public Lazy<Manager> Manager { get; internal set; }
}
在上面的例子中,消费者可能只需要使用Name属性,但是必须填充可能使用也可能不使用的字段,这可能是延迟加载的地方。我说could而不是should,因为总是有这样的情况:预先加载所有内容可能会更高效....