问题 C#中的自我分配


我正在查看我刚才写的一些代码,并意识到我在C#中对赋值运算符做了一个假设。这是有问题的代码行(它按预期工作):

pointsChecked = pointsChecked ?? new List<Point>();

pointsChecked 是一个List,指定为递归函数的参数。它是具有默认值的默认参数 null。我想要做的是初始化一次然后构建我已经检查过的点集合,因此它应该只在第一次迭代期间初始化。

我的假设是C#以与C ++相同的方式防止自我分配 operator=  应该 超载时提供警卫(即, if(this == &rightHandSide) return *this;)。但是,我无法找到任何明确说明C#属实的资源。

我发现的最接近的例子是 关于null-coalescing运算符的这个问题, 在哪里 出现 如果不是,则将对象分配回自身 null。在这个例子中,没有人谈到自我任命,但我想确定这不是一个坏习惯,并且没有负面的副作用。

在MSDN上搜索 我还发现(根据我的理解释义)右侧的值被复制到左侧的值并返回。因此,我再次不确定自我指派是否是一件坏事。

我知道我可以做以下事情更安全:

if(pointsChecked == null)
{
    pointsChecked = new List<Point>();
}

但我宁愿理解自我分配的实际情况。


11417
2017-09-15 18:10


起源

尝试优化字段上的自我赋值(不是普通的局部变量)可能会对多线程代码产生一些非常重要的影响。不确定CLR内存模型是否允许优化。 - CodesInChaos
C#不是C ++。在重载赋值运算符中防止自我赋值的整个想法毫无意义。当赋值它们的变量被赋值时,对象不能变异,它或者是指向某个对象的对象的引用,或者是由该类型的另一个实例的内容覆盖值类型的内容。 - millimoose
@millimose我意识到了。我不是要求防止自我分配,你甚至不能在C#中重载赋值运算符。我说的是我假设在幕后有一个类似的结构,并希望对实际发生的事情做一些澄清。 - E. Moffat
@prettycooldevguy幕后花絮 pointsChecked 变量将保持其原始值,或者保持对象句柄 List 如果原始值是 0。原始值是否自我赋值并不重要,因为它只是一个指针或一个不透明的对象引用。无论发生什么都不重要 - 这对你来说并不完全透明,只要C#中不存在自我赋值的概念,它就会成为C#实现中的一个错误。 - millimoose


答案:


赋值复制对象的引用,而不是对象内容。没有可自定义的代码作为对包含对象引用的变量的赋值的一部分运行。结构也是如此。

在C ++中,赋值是可自定义的,在C#中则不是。

将相同的对象引用分配给已经拥有它的变量是安全的:

object someRef = new object();
someRef = someRef; //always does nothing

这与分配任何其他值一样安全:

int someVal = 123;
someVal = someVal; //always does nothing

请注意,克隆/复制对象没有通用的方法。任何依赖于这种机制存在的解释都必须是错误的。

自我指派是安全的,因为它转换为以下近似的IL指令:

ldloc someRef
stloc someRef

这明确定义了语义。它首先加载 someRef 进入堆栈,然后存储堆栈中的任何内容 someRef


10
2017-09-15 18:29



有意思的是,你可以详细了解当你超载时会发生什么 operator = 在结构上以及它如何(或不可能)影响这个? - Scott Chamberlain
赋值运算符不能重载。对于ref类型和结构都没有。结构副本是指定的IL指令,它指示运行时执行按位复制。 - usr
啊,我明白了。去向您展示我多久尝试重载运算符。 - Scott Chamberlain
+1。请注意“C ++赋值是可自定义的”是不完整的 - C ++ - 指向赋值不能重载(指向对象的指针代表什么 someVar 在 SomeNonValueType someVar 基本上是)... - Alexei Levenkov
谢谢,我想我只是在考虑它。很高兴知道,我还没有看到关于SO的另一个解释,所以希望这也有助于其他人! - E. Moffat