在这种情况下,. 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");
        }
    }

在这种情况下,. net Lazy类的使用是不是有点过头了?

这可能有点过分了。

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,因为总是有这样的情况:预先加载所有内容可能会更高效....