3

我最近在 .Net 中了解了 Lazy 类,并且可能已经过度使用它。我在下面有一个示例,其中可以以急切的方式评估事物,但是如果一遍又一遍地调用,这将导致重复相同的计算。在这个特定示例中,使用 Lazy 的成本可能无法证明其好处,我对此不确定,因为我还不了解 lambda 和惰性调用的成本。我喜欢使用链接的 Lazy 属性,因为我可以将复杂的逻辑分解成小的、可管理的块。我也不再需要考虑初始化东西的最佳位置在哪里——我需要知道的是,如果我不使用它们,它们将不会被初始化,并且会在我开始使用它们之前被初始化一次。然而,一旦我开始使用惰性和 lambda,原本简单的类现在变得更加复杂。我无法客观地决定何时这是合理的,何时在复杂性、可读性、可能的速度方面过度杀伤。您的一般建议是什么?

    // 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");
        }
    }
4

5 回答 5

2

您说“我不再需要考虑初始化东西的最佳位置在哪里”

这是一个不好的习惯。您应该确切地知道程序中发生了什么

当需要传递一个对象但需要一些计算时,您应该Lazy<> 。所以只有当它被使用时才会被计算。

除此之外,您需要记住,您使用惰性检索的对象不是请求时处于程序状态的对象。
只有在使用对象时,您才会获得对象本身。如果您获得对程序状态很重要的对象,这将很难在以后调试。

于 2011-10-07T17:06:12.240 回答
2

这可能有点矫枉过正。

当泛型类型的创建或评估成本很高时,和/或在依赖类的每次使用中并不总是需要泛型类型时,通常应该使用惰性。

很可能,任何在这里调用你的 getter 的东西都需要在调用你的 getter 时立即得到一个实际的字符串值。在这种情况下返回 Lazy 是不必要的,因为调用代码会立即评估 Lazy 实例以获得它真正需要的内容。Lazy 的“即时”特性在这里被浪费了,因此,YAGNI(你不需要它)。

也就是说,Lazy 固有的“开销”并没有那么多。Lazy 只不过是一个引用将生成泛型类型的 lambda 的类。Lambda 的定义和执行成本相对较低;它们只是方法,编译时由 CLR 赋予混搭名称。额外类的实例化是主要的推动因素,即使这样也不可怕。但是,从编码和性能的角度来看,这都是不必要的开销。

于 2011-10-07T17:08:13.167 回答
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<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"; }
}
于 2012-07-15T16:17:26.077 回答
0

Lazy用于创建简单的字符串属性确实是一种矫枉过正。使用 lambda 参数初始化实例Lazy可能比执行单个字符串操作要昂贵得多。还有一个其他人尚未提及的更重要的论点 - 请记住,编译器将 lambda 参数解析为非常复杂的结构,远比字符串连接复杂得多。

于 2011-10-07T17:10:46.037 回答
0

另一个适合使用延迟加载的领域是可以在部分状态下使用的类型。例如,考虑以下情况:

public class Artist
{
     public string Name { get; set; }
     public Lazy<Manager> Manager { get; internal set; }
}

在上面的示例中,消费者可能只需要使用我们的 Name 属性,但必须填充可能使用或不使用的字段可能是延迟加载的地方。我说不应该,因为总有一些情况下,预先加载所有内容可能会更好......取决于您的应用程序需要做什么。

于 2011-10-07T17:24:48.337 回答