Is the use of .Net Lazy class an overkill in this case?
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, m开发者_开发知识库anageable 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");
}
}
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.
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.
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"; }
}
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.
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.
精彩评论