Reputation: 1734
This is a follow-up to my post, Is this a correct implementation of a concurrent observable collection?.
In that post I have a custom class that implements a generic concurrent observable list, including an implementation of IEnumerable<T>.GetEnumerator()
. This was the original code:
public IEnumerator<T> GetEnumerator()
{
var localSnapshot = _snapshot; //create local variable to protect enumerator, if class member (_snapshot) should be changed/replaced while iterating
return ((IEnumerable<T>)localSnapshot).GetEnumerator();
}
With _snapshot
being a private Array
field that is rebuilt using a lock()
whenever the actual inner collection (List<T> _list
) is modified.
But now I think the localSnapshot
variable is not required at all, the code should be:
public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)_snapshot).GetEnumerator();
}
Because localSnapshot
is simply assigned a reference to the same address that _snapshot
references. GetEnumerator
doesn't care (and cannot tell) which variable was used (and will for its own use, of course, create yet another variable that references the same array).
If my above assumption is correct, I wonder, if the compiler would optimize the variable away? Then the resulting code would be identical. Or, if not: could copying the reference theoretically even be "harmful", because the copy will be less up-to-date than it could be (another thread could refresh _snapshot
after the copy was made, but before GetEnumerator
is called)? And, is this "side-effect" the reason the compiler does not optimize the code - because optimizations are to be "side-effect-free"?
Upvotes: 4
Views: 729
Reputation: 39007
The two versions of the code will produce the same result after compilation. As TheGeneral pointed out in the comments, a good way to make sure is to check on sharplab.io.
Note that this is true only if you compile in Release mode. If you compile in Debug mode, then the compiler will assume you might need the intermediary variable for debugging purpose and won't optimize it.
could copying the reference theoretically even be "harmful", because the copy will be less up-to-date than it could be (another thread could refresh _snapshot after the copy was made, but before GetEnumerator is called)
This would be the case if you had some code executing between var localSnapshot = _snapshot;
and return ((IEnumerable<T>)localSnapshot).GetEnumerator()
. In that situation the optimization would not have been possible. Otherwise, in both cases you're reading the value and directly using it. There is no difference of "freshness" between the two versions of the code.
Upvotes: 6