在这种情况下,.Net Lazy类的使用是否过度杀伤?

时间:2011-10-07 16:59:11

标签: c# lazy-evaluation lambda lazy-initialization

我最近在.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");
        }
    }

5 个答案:

答案 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属性,但是必须填充可能使用或不使用的字段可能是延迟加载的地方。我说可以不应该,因为总有一些情况可能会更高效地加载所有前端......取决于你的应用程序需要做什么。