mike
mike

Reputation: 1734

Will the C# compiler optimize variable away?

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

Answers (1)

Kevin Gosse
Kevin Gosse

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

Related Questions