Reputation: 1867
The following cache helper takes a key name and a creator function. If the cache already contains the named object then it is returned, otherwise the creator is invoked to create the object, stick it in the cache and return the new object.
public static T GetObject<T>(String name, Func<T> creator) { ... }
Obviously the creator function will get called once (the first time), and the other 999 times will just get ignored.
What are the performance implications? If this was just a method pointer then I assume it will be quite trivial - just pushing a pointer on the stack each time - but what if the lambda is expensive, maybe a closure with state - how much work actually gets done every time we call this after the object is already created (pushing the lambda on the stack just to be ignored).
Is there an alternative strategy to this?
Upvotes: 1
Views: 115
Reputation: 2233
Let's first give your method an implementation, then we can look at what's going on under the covers.
private static Dictionary<string, object> _LUT;
public static T GetObject<T>(String name, Func<T> creator)
{
object obj;
if (_LUT.TryGetValue(name, out obj))
{
return (T)obj;
}
T ret = creator();
_LUT.Add(name, ret);
return ret;
}
public string GetString(string name)
{
return GetObject<string>(name, () => "Foo");
}
Instructions for GetObject(...):
IL_0000: ldsfld System.Collections.Generic.Dictionary`2[System.String,System.Object] _LUT
IL_0005: ldarg.0
IL_0006: ldloca.s System.Object (0)
IL_0008: callvirt Boolean TryGetValue(System.String, System.Object ByRef)
IL_000d: brfalse.s IL_0016
IL_000f: ldloc.0
IL_0010: unbox.any T
IL_0015: ret
IL_0016: ldarg.1
IL_0017: callvirt T Invoke()
IL_001c: stloc.1
IL_001d: ldsfld System.Collections.Generic.Dictionary`2[System.String,System.Object] _LUT
IL_0022: ldarg.0
IL_0023: ldloc.1
IL_0024: box T
IL_0029: callvirt Void Add(System.String, System.Object)
IL_002e: ldloc.1
IL_002f: ret
Instructions for GetString(...):
IL_0000: ldarg.1
IL_0001: ldsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_0006: brtrue.s IL_0019
IL_0008: ldnull
IL_0009: ldftn System.String <GetString>b__0()
IL_000f: newobj Void .ctor(System.Object, IntPtr)
IL_0014: stsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_0019: ldsfld System.Func`1[System.String] CS$<>9__CachedAnonymousMethodDelegate1
IL_001e: call System.String GetObject[String](System.String, System.Func`1[System.String])
IL_0023: ret
As you can see, your lambda is being stored in a static field of type System.Func1[System.String]
which is being passed to your method by reference (Why are delegates reference types?), and calling System.Func1[System.String].Invoke()
when needed, so the size of the method should make no difference.
As for your question on whether an alternative exists:
If this is a one-time function you could try System.Lazy
, if you're looking for general Caching support I suggest checking out this: .NET 4 Caching Support
Upvotes: 2