Reputation: 3875
Consider
public List<GroupByDateType> GroupByItems
{
get { return Enum.GetValues(typeof(GroupByDateType)).Cast<GroupByDateType>().ToList(); }
}
And
private List<GroupByDateType> _GroupByItems;
public List<GroupByDateType> GroupByItems
{
get {return _GroupByItems??(_GroupByItems=Enum.GetValues(typeof(GroupByDateType)).Cast<GroupByDateType>().ToList());}
}
Usually I go with the second one because when I look at the first one it seems to me that the list is regenerated, but is that really what happens, does the CLR create the backing field for me by any chance?
Upvotes: 2
Views: 105
Reputation: 113352
No. Though the both the compiler and the jitter are free to perform all manner of optimisations, as a rule the compiler only performs those that are most obvious (in particular, removing dead code), while the jitter tends not to do anything which would add to the layout or have a downside the developer chose to avoid.
Memoisation changes the layout by adding a field, and optimises for speed at the cost of memory and so is something you don't want to happen every time (if you consider the number of repeat calls times the cost of calculating to not be worth the extra memory used). Among other things, deciding on that balance involves factors outside of just this code, such as how many such objects will be created (and hence how much memory used). It wouldn't be a good automatic change across a large number of cases.
There's also the fact that memoisation is easy; if you'd wanted it, you'd have done it, if you didn't want it you wouldn't have (and indeed, certainly shouldn't every time, only when it has a real advantage).
Conversely, memorisation that covers null as a possible result is slightly trickier, and that which must be threadsafe trickier again (there's a few different approaches that work in different concurrent cases, all of which are easy in themselves, but picking which to use often isn't). It would be totally inappropriate for an "optimisation" to turn a method (including a property getter) that was threadsafe into one that was not, or at least changed the behaviour of the object in the face of concurrent calls.
Memoisation can also sometimes have subtle semantic differences such as your example; the behaviour differs if calling code changes the list. This could be vital (the list is examined for such changes by other code) or a bug (the next caller gets a changed list that is incorrect) or irrelevant (it's internal-only, and you don't change it anywhere so you're cutting out the cost of safeguarding). Not all memoisation has this issue (one could return a read only list, for example), but it does mean it couldn't be done automatically.
Upvotes: 4
Reputation: 125660
No, there is not backing field created, because property is not an auto-implemented property.
private List<GroupByDateType> _GroupByItems;
public List<GroupByDateType> GroupByItems
{
get { return _GroupByItems ?? (_GroupByItems = Enum.GetValues(typeof(GroupByDateType)).Cast<GroupByDateType>().ToList()); }
}
public List<GroupByDateType> GroupByItems2
{
get { return Enum.GetValues(typeof(GroupByDateType)).Cast<GroupByDateType>().ToList(); }
}
If you try to check generated IL you'll see only one backing field, the one you defined in second version of the property:
The getter only calls all specified methods, without storing results somewhere:
.method public hidebysig specialname
instance class [mscorlib]System.Collections.Generic.List`1<valuetype Program/GroupByDateType> get_GroupByItems2 () cil managed
{
// Method begins at RVA 0x2052
// Code size 26 (0x1a)
.maxstack 8
IL_0000: ldtoken Program/GroupByDateType
IL_0005: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_000a: call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
IL_000f: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Cast<valuetype Program/GroupByDateType>(class [mscorlib]System.Collections.IEnumerable)
IL_0014: call class [mscorlib]System.Collections.Generic.List`1<!!0> [System.Core]System.Linq.Enumerable::ToList<valuetype Program/GroupByDateType>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0019: ret
} // end of method Program::get_GroupByItems2
where for the one with a field you'll get additional assignment and null check:
.method public hidebysig specialname
instance class [mscorlib]System.Collections.Generic.List`1<valuetype Program/GroupByDateType> get_GroupByItems () cil managed
{
// Method begins at RVA 0x2070
// Code size 45 (0x2d)
.maxstack 3
.locals init (
[0] class [mscorlib]System.Collections.Generic.List`1<valuetype Program/GroupByDateType> CS$0$0000
)
IL_0000: ldarg.0
IL_0001: ldfld class [mscorlib]System.Collections.Generic.List`1<valuetype Program/GroupByDateType> Program::_GroupByItems
IL_0006: dup
IL_0007: brtrue.s IL_002c
IL_0009: pop
IL_000a: ldarg.0
IL_000b: ldtoken Program/GroupByDateType
IL_0010: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0015: call class [mscorlib]System.Array [mscorlib]System.Enum::GetValues(class [mscorlib]System.Type)
IL_001a: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Cast<valuetype Program/GroupByDateType>(class [mscorlib]System.Collections.IEnumerable)
IL_001f: call class [mscorlib]System.Collections.Generic.List`1<!!0> [System.Core]System.Linq.Enumerable::ToList<valuetype Program/GroupByDateType>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0024: dup
IL_0025: stloc.0
IL_0026: stfld class [mscorlib]System.Collections.Generic.List`1<valuetype Program/GroupByDateType> Program::_GroupByItems
IL_002b: ldloc.0
IL_002c: ret
} // end of method Program::get_GroupByItems
Upvotes: 2
Reputation: 12683
NO, the compiler will treat those two statements completely different.
In the first statement you are re-evalute the List<T>
on every property get request. In the second statement you are essentially writing this (in shorthand).
private List<GroupByDateType> _GroupByItems;
public List<GroupByDateType> GroupByItems
{
get
{
if(_GroupByItems != null)
return _GroupByItems;
_GroupByItems=Enum.GetValues(typeof(GroupByDateType)).Cast<GroupByDateType>().ToList());
return _GroupByItems;
}
}
If you review the re-written sample this is how your one liner would execute.
Basically with the double question marks (??) null coalescing operator the compiler will execute the code after the ??
if the instance of _GroupByItems
(the object or statement before the ??
) is null.
If the _GroupByItems
instance is not null then the _GroupByItems
is returned. It is also worth noting that the second statement (after the ??) is actually setting the instance of _GroupByItems
so on the next call the instance of the object is returned.
Finally, as the instance is set all subsequent calls will use the instance of the List and will no re-evaluate the list. This means if the list changes (although this example it wouldnt as they are enums) you must set the instance _GroupByItems
to null (or the new instance of the list).
Hope this helps.
Upvotes: 3