C#编译器会优化掉变量吗?

4
这是我之前发布的文章"这是一个并发可观察集合的正确实现吗?"的跟进内容。
在那篇文章中,我有一个自定义类,实现了一个通用并发可观察列表,包括IEnumerable<T>.GetEnumerator()的实现。这是原始代码:
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();
}

当实际内部集合(List<T> _list)被修改时,_snapshot是一个私有的Array字段,使用lock()重建。

但现在我认为根本不需要localSnapshot变量,代码应该是:

public IEnumerator<T> GetEnumerator()
{
    return ((IEnumerable<T>)_snapshot).GetEnumerator();
}

因为localSnapshot只是分配了一个指向与_snapshot相同地址的引用。 GetEnumerator并不关心(也无法知道)使用了哪个变量(当然,它将为自己使用创建另一个变量,该变量引用相同的数组)。
如果我上面的假设是正确的,我想知道编译器是否会优化掉这个变量?那么生成的代码将是相同的。或者,如果不能:复制引用在理论上甚至可能会是“有害”的,因为复制将比它可以更及时地更新(另一个线程可以在复制完成后但在调用GetEnumerator之前刷新_snapshot)。而且,这种“副作用”是编译器不优化代码的原因吗——因为优化应该是“无副作用”的?

1
为了可靠地回答这个问题,你需要自己进行测试。语言编译器或者最可能的是Jitter可能会将其优化掉。如果你发现自己在问这些问题,请检查发布模式下的IL,同时你可能想使用https://sharplab.io/或其他工具来检查汇编代码。 - TheGeneral
在这种情况下,优化将由编译器完成。同一代码的两个版本将产生相同的 IL,这两个代码片段之间在功能上没有区别。 - Kevin Gosse
1个回答

4
两个版本的代码在编译后将产生相同的结果。正如TheGeneral在评论中指出的那样,确保的一个好方法是检查sharplab.io
请注意,这仅在您以发布模式编译时成立。如果您在调试模式下进行编译,则编译器将假定您可能需要中介变量进行调试,并且不会对其进行优化。
引用:
理论上,复制引用甚至可能是“有害”的,因为副本将比它可以(另一个线程可以在制作副本之后但在调用GetEnumerator之前刷新_snapshot)更新得更少。
如果在var localSnapshot = _snapshot;return ((IEnumerable<T>)localSnapshot).GetEnumerator()之间执行了一些代码,那么情况就是这样。在这种情况下,优化将不可能实现。否则,在两种情况下,您都在读取值并直接使用它。两个版本的代码之间没有“新鲜度”的区别。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接