在这种情况下,使用 .Net Lazy 类是否太过分了?

发布于 2024-12-08 11:44:28 字数 1153 浏览 1 评论 0原文

我最近了解了 .Net 中的 Lazy 类,并且可能已经过度使用它了。我在下面有一个示例,其中可以以急切的方式对事物进行评估,但是如果一遍又一遍地调用,这将导致重复相同的计算。在这个特定的示例中,使用 Lazy 的成本可能无法证明其好处,而且我对此不确定,因为我还不了解 lambda 和延迟调用到底有多昂贵。我喜欢使用链式惰性属性,因为我可以将复杂的逻辑分解成小的、可管理的块。我也不再需要考虑初始化东西的最佳位置在哪里 - 我需要知道的是,如果我不使用它们,东西将不会被初始化,并且在我开始使用它们之前将被初始化一次。然而,一旦我开始使用惰性和 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");
        }
    }

I learned about Lazy class in .Net recently and have been probably over-using it. I have an example below where things could have been evaluated in an eager fashion, but that would result in repeating the same calculation if called over and over. In this particular example the cost of using Lazy might not justify the benefit, and I am not sure about this, since I do not yet understand just how expensive lambdas and lazy invocation are. I like using chained Lazy properties, because I can break complex logic into small, manageable chunks. I also no longer need to think about where is the best place to initialize stuff - all I need to know is that things will not be initialized if I do not use them and will be initialized exactly once before I start using them. However, once I start using lazy and lambdas, what was a simple class is now more complex. I cannot objectively decide when this is justified and when this is an overkill in terms of complexity, readability, possibly speed. What would your general recommendation be?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

戴着白色围巾的女孩 2024-12-15 11:44:28

这可能有点矫枉过正了。

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

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

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

This is probably a little bit of overkill.

Lazy should usually be used when the generic type is expensive to create or evaluate, and/or when the generic type is not always needed in every usage of the dependent class.

More than likely, anything calling your getters here will need an actual string value immediately upon calling your getter. To return a Lazy in such a case is unnecessary, as the calling code will simply evaluate the Lazy instance immediately to get what it really needs. The "just-in-time" nature of Lazy is wasted here, and therefore, YAGNI (You Ain't Gonna Need It).

That said, the "overhead" inherent in Lazy isn't all that much. A Lazy is little more than a class referencing a lambda that will produce the generic type. Lambdas are relatively cheap to define and execute; they're just methods, which are given a mashup name by the CLR when compiled. The instantiation of the extra class is the main kicker, and even then it's not terrible. However, it's unnecessary overhead from both a coding and performance perspective.

别靠近我心 2024-12-15 11:44:28

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

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

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

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

You said "i no longer need to think about where is the best place to initialize stuff".

This is a bad habit to get in to. You should know exactly what's going on in your program

You should Lazy<> when there's an object that needs to be passed, but requires some computation.
So only when it will be used it will be calculated.

Besides that, you need to remember that the object you retrieve with the lazy is not the object that was in the program's state when it was requested.
You'll get the object itself only when it will be used. This will be hard to debug later on if you get objects that are important to the program's state.

在风中等你 2024-12-15 11:44:28

这似乎并不是为了节省昂贵对象的创建/加载而使用 Lazy,而是为了(可能无意中)包装一些任意委托以延迟执行。您可能希望/打算派生属性 getter 返回的是 string,而不是 Lazy 对象。

如果调用代码看起来像

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 不适用于公共属性;这里你的 getter 返回新的(如全新的、不同的、额外的)Lazy 对象,因此每个调用者将(可能)获得一个不同的 。价值!您的所有 Lazy 属性均取决于首次访问其 .Value 时设置的 DatFileName,因此您总是需要考虑相对于每个派生属性的使用何时进行初始化。

请参阅 MSDN 文章“延迟初始化”,该文章创建了私有 Lazy 支持变量和公共属性 getter,如下所示:

get { return _privateLazyObject.Value; }

我可能猜测您的代码应该/可能喜欢什么,使用 Lazy 来定义您的“设置一次”基本属性:

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

This does not appear to be using Lazy<T> for the purpose of saving creation/loading of an expensive object so much as it is to (perhaps unintentionally) be wrapping some arbitrary delegate for delayed execution. What you probably want/intend your derived property getters to return is a string, not a Lazy<string> object.

If the calling code looks like

string fileName = MicrosoftFormatName.Value;

then there is obviously no point, since you are "Lazy-Loading" immediately.

If the calling code looks like

var lazyName = MicrosoftFormatName; // Not yet evaluated
// some other stuff, maybe changing the value of DatFileName
string fileName2 = lazyName.Value;

then you can see there is a chance for fileName2 to not be determinable when the lazyName object is created.

It seems to me that Lazy<T> isn't best used for public properties; here your getters are returning new (as in brand new, distinct, extra) Lazy<string> objects, so each caller will (potentially) get a different .Value! All of your Lazy<string> properties depend on DatFileName being set at the time their .Value is first accessed, so you will always need to think about when that is initialized relative to the use of each of the derived properties.

See the MSDN article "Lazy Initialization" which creates a private Lazy<T> backing variable and a public property getter that looks like:

get { return _privateLazyObject.Value; }

What I might guess your code should/might like, using Lazy<string> to define your "set-once" base property:

// 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"; }
}
沉鱼一梦 2024-12-15 11:44:28

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

Using Lazy for creating simple string properties is indeed an overkill. Initializing the instance of Lazy with lambda parameter is probably much more expensive than doing single string operation. There's one more important argument that others didn't mention yet - remember that lambda parameter is resolved by the compiler to quite complex structure, far more comples than string concatenation.

兔小萌 2024-12-15 11:44:28

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

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

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

The other area that is good to use lazy loading is in a type that can be consumed in a partial state. As an example, consider the following:

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

In the above example, consumers may only need to utilise our Name property, but having to populate fields which may or may not be used could be a place for lazy loading. I say could not should, as there are always situations when it may be more performant to load all up front.... depending on what your application needs to do.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文